daigo3

And we forget because we must, And not because we will.

Running TensorFlow Serving #1

Steps

  • Keras モデルを TensorFlow Serving が扱えるフォーマットに変換する
  • TensorFlow Serving をローカルで実行する
  • 画像を前処理、TensorFlow Serving と通信するサービスを構築する
  • これらのモデルを Kubernetes 上にデプロイする

次に Kubeflow を使用し、より簡単にデプロイできるようにする。

  • KFServing を使用したモデルのサービング
  • 画像の前処理のための transformer と 予測のための後処理

saved_model フォーマットへの変換

Keras でトレーニングしたモデルは h5 という形式で保存されているので、 saved_model というフォーマットに変換する。

# model_converter.py

import tensorflow as tf
from tensorflow import keras

model = keras.models.load_model('/path/to/model.h5')
tf.saved_model.save(model, 'clothing-model')
➜ pip install tensorflow

➜ python model_converter.py

clothing-model ディレクトリができて、その中に saved_model 形式のモデルが生成される。

➜ tree clothing-model
clothing-model
├── assets
├── saved_model.pb
└── variables
    ├── variables.data-00000-of-00001
    └── variables.index

TF Serving を利用する際、以下の情報が必要になるので、saved_model_cli で調べる

  • Model signature
  • 入力層の名前
  • 出力層の名前
➜ saved_model_cli show --dir clothing-model --all

MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['__saved_model_init_op']:
  The given SavedModel SignatureDef contains the following input(s):
  The given SavedModel SignatureDef contains the following output(s):
    outputs['__saved_model_init_op'] tensor_info:
        dtype: DT_INVALID
        shape: unknown_rank
        name: NoOp
  Method name is: 

signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['input_8'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 299, 299, 3)
        name: serving_default_input_8:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['dense_7'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 10)
        name: StatefulPartitionedCall:0
  Method name is: tensorflow/serving/predict

Defined Functions:
  Function Name: '__call__'
    Option #1
      Callable with:
        Argument #1
          input_8: TensorSpec(shape=(None, 299, 299, 3), dtype=tf.float32, name='input_8')
        Argument #2
          DType: bool
          Value: False
        Argument #3
          DType: NoneType
          Value: None
    Option #2
      Callable with:
        Argument #1
          inputs: TensorSpec(shape=(None, 299, 299, 3), dtype=tf.float32, name='inputs')
        Argument #2
          DType: bool
          Value: False
        Argument #3
          DType: NoneType
          Value: None
    Option #3
      Callable with:
        Argument #1
          input_8: TensorSpec(shape=(None, 299, 299, 3), dtype=tf.float32, name='input_8')
        Argument #2
          DType: bool
          Value: True
        Argument #3
          DType: NoneType
          Value: None
    Option #4
      Callable with:
        Argument #1
          inputs: TensorSpec(shape=(None, 299, 299, 3), dtype=tf.float32, name='inputs')
        Argument #2
          DType: bool
          Value: True
        Argument #3
          DType: NoneType
          Value: None

  Function Name: '_default_save_signature'
    Option #1
      Callable with:
        Argument #1
          input_8: TensorSpec(shape=(None, 299, 299, 3), dtype=tf.float32, name='input_8')

  Function Name: 'call_and_return_all_conditional_losses'
    Option #1
      Callable with:
        Argument #1
          input_8: TensorSpec(shape=(None, 299, 299, 3), dtype=tf.float32, name='input_8')
        Argument #2
          DType: bool
          Value: False
        Argument #3
          DType: NoneType
          Value: None
    Option #2
      Callable with:
        Argument #1
          input_8: TensorSpec(shape=(None, 299, 299, 3), dtype=tf.float32, name='input_8')
        Argument #2
          DType: bool
          Value: True
        Argument #3
          DType: NoneType
          Value: None
    Option #3
      Callable with:
        Argument #1
          inputs: TensorSpec(shape=(None, 299, 299, 3), dtype=tf.float32, name='inputs')
        Argument #2
          DType: bool
          Value: True
        Argument #3
          DType: NoneType
          Value: None
    Option #4
      Callable with:
        Argument #1
          inputs: TensorSpec(shape=(None, 299, 299, 3), dtype=tf.float32, name='inputs')
        Argument #2
          DType: bool
          Value: False
        Argument #3
          DType: NoneType
          Value: None

先頭の

signature_def['serving_default']: # signature
  The given SavedModel SignatureDef contains the following input(s):
    inputs['input_8'] tensor_info: # input layer
        dtype: DT_FLOAT
        shape: (-1, 299, 299, 3)
        name: serving_default_input_8:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['dense_7'] tensor_info: # output layer
        dtype: DT_FLOAT
        shape: (-1, 10)
        name: StatefulPartitionedCall:0
  Method name is: tensorflow/serving/predict

から

  • signatureの定義は serving_default
  • 入力層は input_8 ()
  • 出力層は dense_7 ()

というのがわかる。

Running TFServing locally

Docker で動かしてみる。

https://www.tensorflow.org/tfx/serving/docker

➜ docker run -it --rm \
    -p 8500:8500 \
    -v "$(pwd)/clothing-model:/models/clothing-model/1" \
    -e MODEL_NAME=clothing-model \
    tensorflow/serving:2.3.0

...
2022-01-15 02:24:12.894817: I tensorflow_serving/model_servers/server.cc:387] Exporting HTTP/REST API at:localhost:8501 ...
[evhttp_server.cc : 238] NET_LOG: Entering the event loop ...

Entering the event loop ... が表示されたら正常に起動してリクエストを受け付けている状態になる。

Invoking the TF Serving model

➜ pip install grpcio tensorflow-serving-api
import grpc
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

from tensorflow_serving.apis import predict_pb2, prediction_service_pb2_grpc
HOST = 'localhost:8500'
channel = grpc.insecure_channel(HOST)
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
def preprocess(name, url):
    img_url = tf.keras.utils.get_file(name, origin=url)
    img = tf.keras.utils.load_img(img_url, target_size=(299,299))
    plt.imshow(img)

    x = tf.keras.utils.img_to_array(img)
    batch = np.expand_dims(x, axis=0)
    batch /= 127.5
    batch -= 1.0
    return batch
def numpy_to_protobuf(data):
    """convert numpy array to protobuf"""
    return tf.make_tensor_proto(data, shape=data.shape)
URL = 'https://dl.dropboxusercontent.com/s/02q2qbwu4yweajm/17169926dg_29_f.jpg'
X = preprocess('margiela-boots-2', URL)
print(X.shape)

> (1, 299, 299, 3)
pb_req = predict_pb2.PredictRequest()

pb_req.model_spec.name = 'clothing-model'
pb_req.model_spec.signature_name = 'serving_default'
pb_req.inputs['input_8'].CopyFrom(numpy_to_protobuf(X))
result = stub.Predict(pb_req, timeout=20.0)
pred = result.outputs['dense_7'].float_val

labels = [
    'dress',
    'hat',
    'longsleeve',
    'outwear',
    'pants',
    'shirt',
    'shoes',
    'shorts',
    'skirt',
    't-shirt'
]

{c: p for c, p in zip(labels, pred)}
{'dress': -0.268576055765152,
 'hat': -2.0750882625579834,
 'longsleeve': -3.4710824489593506,
 'outwear': -1.9989770650863647,
 'pants': -0.18006758391857147,
 'shirt': -1.1924179792404175,
 'shoes': 5.7457098960876465,
 'shorts': -1.4281518459320068,
 'skirt': -1.4905409812927246,
 't-shirt': -0.20355109870433807}

Jupyter に Kernel を追加する

やりたいこと

venv で作成した仮想環境で Jupyter を使いたい

➜ python3 -m venv envsource ./env/bin/activate

➜ pip install ipykernel

➜ python -m ipykernel install --name test-env

➜ jupyter kernelspec list
...
test-env           /usr/local/share/jupyter/kernels/test-env

pandas はインストールしてないので使えない:

➜ pip install pandas

使えるようになる:

Pandas の to_csv の保存先を s3 にする

SageMaker の Notebook から DataFrame を CSV にして S3 に保存する。

import pandas as pd
import boto3
from sagemaker import get_execution_role

role = get_execution_role()
bucket = 'BUCKET_NAME'

df = pd.DataFrame([1,2,3,4])

data_location = 's3://{}/results/test.csv'.format(bucket)

df.to_csv(data_location, index=False)

ES Modules / CommonJS Modules

ES Modules == ESM
CommonJS Modules == CJS

ESM では以下のようにモジュールを読み込める:

import { foo } from 'bar'

CJS の場合は:

const { foo } = require('foo')

両者の違い:

ESM CJS
ブラウザ互換 Node.js
default で Strict not Strict
Async Sync

Node.js で実行する場合、まず拡張子でどちらのモジュールシステムが使われているかが判定される :

// foo.cjs
console.log(typeof module); // object

// foo.mjs
console.log(typeof module); // undefined

あるいは package.jsontype フィールドで指定も可能:

// package.json
{
  ...
  "type": "commonjs"
  ...
}

// foo.js
console.log(typeof module);
➜ node foo.js
object
// package.json
{
  ...
  "type": "commonjs"
  ...
}

// foo.js
console.log(typeof module);
➜ node foo.js
undefined

test

hello, world

const fn = () => 'hello'