Help us understand the problem. What is going on with this article?

機械学習モデルをSeldon Coreを使いKubernetesでサービス化

More than 1 year has passed since last update.

はじめに

 機械学習(ML: Machine Learning)を勉強し始めの人にありがちなケースとして、MNISTなどのチュートリアルを使ってアルゴリズムなどを勉強してはみるものの、その後、機械学習をつかったサービスを作ろうとして挫折する人も多いかと思います。どれだけ機械学習のアルゴリズムを勉強しても、画像認識を使ったスマホアプリやWebサービスを作るのは困難ではないでしょうか。サービス化を行うためには、機械学習にて学習したモデルを使った画像認識などのAPIサーバを立ち上げ、アクセスが集中した際に性能がスケールできるようにインフラも含めてサービスを構築することも重要です。しかし、機械学習の知識と、サービス化する知識は違うため、なかなか両立できている人は少ないのが現状です。言い方を変えると、AIのアルゴリズム、確率統計学や各産業のドメインナレッジを強みとするデータサイエンティストと、Web技術やクラウドなどのインフラ技術を強みとするアプリ開発者・インフラ開発者がもつ知識やスキルセットは根本的に違うため、両方を習得するのはハードルが高くなります。特に海外の求人情報では、明確に職業が分かれているのもそのためです。
 しかし、インフラ技術の知識は少ないものの、機械学習を勉強したからにはサービス化もしたいと思う人も多いかと思います。そこで、今回は、機械学習モデルを容易にKubernetes上でREST APIやgRPCのサービスにしてくれるSeldon Coreを試します。Seldonは、Kubernetes上で機械学習の環境一式を提供するKubeflowでも採用されています。Seldonの特徴は以下のようなものがあります。

  • 様々な機械学習のライブラリ、マイクロサービスやコンテナのフレームワークを利用可能
  • 容易にスケールでき、快適に10億回/月規模の推論が可能
  • Kubernetesが動作しているクラウド環境であれば、パブリッククラウドでもプライベートクラウドでも利用可能

環境

今回の検証では、以下の環境を使います。

[機械学習モデルの開発環境]
- Mac (macOS 10.12.6)
- Docker Version 18.06.1-ce-mac73

[クラウド環境]
- Kubernetes v1.11.1
  - Master x 1, Worker x 2
- Helm v2.9.1

[コンテナレポジトリ]
- DockerHub

セットアップ

セットアップでは、クラウド環境のKubernetesへSeldon Coreを、開発で利用するMacへs2iコマンドをそれぞれインストールします。また、この検証では、Seldon CoreのインストールにHelmを使うので、Helmをインストールしていない人は事前にインストールしておきます。

s2iのインストール

まず、ソースコードからコンテナイメージを作成するコマンドであるs2iをMacへインストールします。
HomeBrewでインストール出来ます。

$ brew install source-to-image

正しくインストールされているかは、以下のコマンドを使って確認して見てください。

$ s2i usage seldonio/seldon-core-s2i-python3:0.3

This is the seldon-core-s2i-python S2I image:
To use it, install S2I: https://github.com/openshift/source-to-image

To create a template application clone https://github.com/seldonio/seldon-core.git and copy the appropriate folder for your needs from wrappers/s2i/python/test

Sample MODEL invocation:
------------------------

s2i build https://github.com/seldonio/seldon-core.git --context-dir=wrappers/s2i/python/test/model-template-app seldonio/seldon-core-s2i-python2 seldon-core-template-model

You can then run the resulting image via:
docker run -p 5000:5000 seldon-core-template-model

And test:
curl  -d 'json={"data":{"ndarray":[[1.0,2.0]]}}' http://0.0.0.0:5000/predict

Sample ROUTER invocation:
-------------------------

s2i build https://github.com/seldonio/seldon-core.git --context-dir=wrappers/s2i/python/test/router-template-app seldonio/seldon-core-s2i-python2 seldon-core-template-router

You can then run the resulting image via:
docker run -p 5000:5000 seldon-core-template-router

And test:
curl  -d 'json={"data":{"ndarray":[[1.0,2.0]]}}' http://0.0.0.0:5000/route
curl  -d 'json={"request":{"data":{"names":["a","b"],"ndarray":[[1.0,2.0]]}},"response":{"meta":{"routing":{"router":0}},"data":{"names":["a","b"],"ndarray":[[1.0,2.0]]}},"reward":1}' http://0.0.0.0:5000/send-feedback


Sample TRANSFORMER invocation:
------------------------------

s2i build https://github.com/seldonio/seldon-core.git --context-dir=wrappers/s2i/python/test/transformer-template-app seldonio/seldon-core-s2i-python2 seldon-core-template-transformer

You can then run the resulting image via:
docker run -p 5000:5000 seldon-core-template-router

And test:
curl  -d 'json={"data":{"ndarray":[[1.0,2.0]]}}' http://0.0.0.0:5000/transform-input
curl  -d 'json={"data":{"ndarray":[[1.0,2.0]]}}' http://0.0.0.0:5000/transform-output

Seldon Coreのデプロイ

Seldon CoreをGitHubからダウンロードします。
2018/11/25時点の最新はv0.2です。

$ git clone https://github.com/SeldonIO/seldon-core.git
Cloning into 'seldon-core'...
...
Resolving deltas: 100% (8165/8165), done.

$ cd seldon-core

以降のセットアップ作業は、seldon-coreディレクトリで実行します。
helmを使ってseldon-core-crdをインストールします。

$  helm install helm-charts/seldon-core-crd --name seldon-core-crd  --set usage_metrics.enabled=true

SeldonのCRD(Custom Resource Definition)がKubernetesへデプロイされているかを確認します。

$ kubectl get crd |grep seldon
seldondeployments.machinelearning.seldon.io   2018-11-25T06:10:23Z

次に、seldon-coreをインストールします。

$ helm install helm-charts/seldon-core --name seldon-core

Seldon CoreのPodとServiceがKubernetesへデプロイされているかを確認します。

$ kubectl get pod |grep seldon
seldon-core-redis-6545d68bc7-jkshg                  1/1     Running   0          2m
seldon-core-seldon-apiserver-7747c689df-xsbjl       1/1     Running   0          2m
seldon-core-seldon-cluster-manager-5f47fcdb-5x4sq   1/1     Running   0          2m

$ kubectl get svc |grep seldon
seldon-core-redis              ClusterIP   10.96.213.175    <none>        6379/TCP                        2m
seldon-core-seldon-apiserver   NodePort    10.109.183.12    <none>        8080:31919/TCP,5000:31328/TCP   2m

以上で、Seldon Coreのインストールは完了です。

モデルの作成

いよいよ、Seldon Coreを使っていきます。
まずは、サービス化する元となる機械学習のモデルを作成します。
ここでは、Seldon Coreにサンプルとして入っているMNISTを利用します。
サンプルのディレクトリexamples/models/deep_mnist/に移動します。

$ cd examples/models/deep_mnist/

このディレクトリのcreate_model.pyがMNISTの学習のプログラムになります。以下に示します。
create_mode.pyはTensorflowのチュートリアルなどで、お馴染みの最急降下法を使った機械学習です。

create_model.py
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot = True)
import tensorflow as tf

if __name__ == '__main__':

    x = tf.placeholder(tf.float32, [None,784], name="x")

    W = tf.Variable(tf.zeros([784,10]))
    b = tf.Variable(tf.zeros([10]))

    y = tf.nn.softmax(tf.matmul(x,W) + b, name="y")

    y_ = tf.placeholder(tf.float32, [None, 10])


    cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))

    train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

    init = tf.initialize_all_variables()

    sess = tf.Session()
    sess.run(init)

    for i in range(1000):
        batch_xs, batch_ys = mnist.train.next_batch(100)
        sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

    correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    print(sess.run(accuracy, feed_dict = {x: mnist.test.images, y_:mnist.test.labels}))

    saver = tf.train.Saver()

    saver.save(sess, "model/deep_mnist_model")

最後の2行がmodelディレクトリ配下に学習後のモデルを保存するコードになります。
create_model.pyを実行します。

$ python create_model.py

実行すると、modelディレクトリが作成され、学習済みの機械学習のモデルがファイルに保存されます。

tree model/
model/
├── checkpoint
├── deep_mnist_model.data-00000-of-00001
├── deep_mnist_model.index
└── deep_mnist_model.meta

コンテナイメージの作成

次に、学習済みのモデルをサービス化するために、s2iを使いコンテナイメージを作成します。
s2iを使いコンテナを作成するためには、以下のファイルを準備する必要があります。

  • Pythonのプログラムファイルと機械学習のモデルファイル一式
  • requirements.txt
  • .s2i/environment

s2iを使ったコンテナイメージの作り方は、開発言語ごとに違いますので、詳しくはこちらを参照してください。

まずは、コンテナを作成するための作業ディレクトリを作成し、機械学習のモデルファイル一式と、それを利用するプログラムが必要になります。今回は、利用するプログラムとして、Seldon Coreにサンプルとして入っているDeepMnist.pyをコピーします。

$ mkdir ~/my-mnist

$ cp -r model ~/my-mnist/model
$ cp DeepMnist.py ~/my-mnist/

$ cd ~/my-mnist

学習済みの機械学習のモデルを読み込んで利用するプログラムDeepMnist.pyは、以下のようになります。
はじめに、tf.train.import_meta_graphsaver.restoreで、学習済みの機械学習のモデルを読み込みます。
predictメソッドが、REST APIで呼び出された際に実行されるメソッドになります。

DeepMnist.py
import tensorflow as tf
import numpy as np

class DeepMnist(object):
    def __init__(self):
        self.class_names = ["class:{}".format(str(i)) for i in range(10)]
        self.sess = tf.Session()
        saver = tf.train.import_meta_graph("model/deep_mnist_model.meta")
        saver.restore(self.sess,tf.train.latest_checkpoint("./model/"))

        graph = tf.get_default_graph()
        self.x = graph.get_tensor_by_name("x:0")
        self.y = graph.get_tensor_by_name("y:0")

    def predict(self,X,feature_names):
        predictions = self.sess.run(self.y,feed_dict={self.x:X})
        return predictions.astype(np.float64)

次に、requirements.txtを作成します。
requirements.txtには、DeepMnist.pyで利用するパッケージ名を記述します。
以下に作成したrequirements.txtを示します。今回のMNISTはTensorflowを使っているので、Tensorflowのパッケージを指定します。

requirements.txt
tensorflow==1.0.1

次に、.s2iディレクトリを作成します。

$ mkdir .s2i
$ cd .s2i

.s2iディレクトリ配下にenvironmentファイルを作ります。
以下に、作成したenvironmentファイルを示します

MODEL_NAME=DeepMnist
API_TYPE=REST
SERVICE_TYPE=MODEL
PERSISTENCE=0

MODEL_NAMEにはDeepMnist.pyのクラス名を指定します。また、API_TYPEにはRESTを指定しています。指定するパラメータの詳しい説明は、こちらを参照してください。
これで準備は完了です。
準備したファイルの一式は、以下のようになっています。

$ tree -a
.
├── .s2i
│   └── environment
├── DeepMnist.py
├── model
│   ├── checkpoint
│   ├── deep_mnist_model.data-00000-of-00001
│   ├── deep_mnist_model.index
│   └── deep_mnist_model.meta
└── requirements.txt

では、s2iコマンドを使って、コンテナイメージを作成してます。

$ s2i build . seldonio/seldon-core-s2i-python3:0.3 ysakashita/deep-mnist:0.1

作成したコンテナイメージをDokerHubへコンテナイメージをPushします。

$ docker push ysakashita/deep-mnist:0.1

これで、学習済みの機械学習のモデルを含んだコンテナイメージが作成できました。

モデルのサービング(Serving)

作成した学習済みの機械学習のモデルのコンテナイメージをKubernetesへデプロイします。
デプロイでは、セットアップにてインストールしたCRD(SeldonDeployment)を使います。
以下に、Manifestファイル(mnist.yaml)を示します。

mnist.yaml
apiVersion: machinelearning.seldon.io/v1alpha2
kind: SeldonDeployment
metadata:
  labels:
    app: seldon
  name: deep-mnist
spec:
  annotations:
    project_name: Deep Mnist
    deployment_version: v1
  name: deep-mnist
  oauth_key: oauth-key
  oauth_secret: oauth-secret
  predictors:
  - componentSpecs:
    - spec:
        containers:
        - image: ysakashita/deep-mnist:0.1
          imagePullPolicy: IfNotPresent
          name: classifier
          resources:
            requests:
              memory: 1Mi
        terminationGracePeriodSeconds: 20
    graph:
      children: []
      name: classifier
      endpoint:
        type: REST
      type: MODEL
    name: single-model
    replicas: 1
    annotations:
      predictor_version: v1

mnist.yamlをデプロイします。

$ kubectl create -f mnist.yaml 
seldondeployment.machinelearning.seldon.io/deep-mnist created

正しくデプロイされているかを確認します。

$ kubectl get SeldonDeployment
NAME         AGE
deep-mnist   1m

$ kubectl get pod |grep deep-mnist
deep-mnist-single-model-classifier-0-5f56bfc996-bdfwn   1/1     Running   0          1m
deep-mnist-single-model-svc-orch-6b6f868448-8qhld       0/1     Running   1          1m

以上で、機械学習のモデルを呼び出すREST APIのサービスが起動できました。

接続テスト

次に、学習済みモデルのREST APIのサービスへの接続テストをします。
接続テストでは、Seldon Coreが用意しているの接続テスト用のプログラムapi_tester
を使います。
api_testerを実行する前に、セットアップでSeldonをダウンロードしたディレクトリのutil/api_testerディレクトリに移動し、makeコマンドを実行します。

$ cd util/api_tester
$ make

これで、api_testerを利用する準備が整いました。
実際に接続テストを行います。
接続先はKubernetesのMaster Node(192.168.0.23)のseldon-core-seldon-apiserverのServiceのポートに対してリクエストします。REST APIでリクエストする内容は、サンプルディレクトリにあるcontract.jsonを利用します。

$ python ./api-tester.py ../../examples/models/deep_mnist/contract.json 192.168.0.23 `kubectl get svc -l app=seldon-apiserver-container-app -o jsonpath='{.items[0].spec.ports[0].nodePort}'` --ambassador-path ""  --oauth-key oauth-key --oauth-secret oauth-secret -p

...
Getting token from http://192.168.0.23:31919/oauth/token
{"access_token":"7219e651-cf1f-4fee-a148-6c0d99bd639e","token_type":"bearer","expires_in":29931,"scope":"read write"}
RECEIVED RESPONSE:
{'meta': {'puid': '9ft0kd97qpnqpic9b1k5dv6jh3', 'tags': {}, 'routing': {}, 'requestPath': {'classifier': 'ysakashita/deep-mnist:0.1'}, 'metrics': []}, 'data': {'names': ['class:0', 'class:1', 'class:2', 'class:3', 'class:4', 'class:5', 'class:6', 'class:7', 'class:8', 'class:9'], 'ndarray': [[0.0016735075041651726, 4.393833989979612e-07, 0.6075786352157593, 0.19233210384845734, 1.9631982013379456e-06, 0.18066911399364471, 0.0006256107590161264, 0.00013788121577817947, 0.016895130276679993, 8.554807573091239e-05]]}}

レスポンスとして、['class:0'...'class:9']とその結果が返ってくれば成功です。

クリーンアップ

Kubernetesへデプロイした学習済みモデルのREST APIのサービスを削除します。

$ kubectl delete -f mnist.yaml 
seldondeployment.machinelearning.seldon.io "deep-mnist" deleted

最後に、Seldon Coreを削除します。

$ helm delete  --purge seldon-core
$ helm delete  --purge seldon-core-crd

おわりに

 今回は、Seldon Coreを使い、機械学習モデルのREST APIのサービスをKubernetesで立ち上げる一連の手順を検証しました。Seldon Coreを利用すると、DjangoやBottleなどを使ってPythonでREST APIのサーバを作成しなくても、容易にREST APIサービスを立ち上げることができます。つまり、機械学習の知識とSeldon Coreさえ使えれば、容易にKubernetes上に機械学習モデルのREST APIサービスを立ち上げることができます。
 また、機械学習では、データやパラメータのチューニングを何度も行い推論の精度を上げていきます。
そのため、一度DeepMnist.py, s2iコマンドで利用するファイル一式(.s2i/environment, requirements.txt), SeldonDeploymentのManifestファイルを作ってしまえば、精度をあげていくのは、以下の作業の繰り返しになります。

  1. 新しいデータを使って学習しモデル生成。
  2. s2iコマンドを使ってコンテナイメージをBuild&Push
  3. Kubernetesへデプロイ

このような繰り返しの作業は、CI/CDに組み込むことで、新たなデータで学習を行うたびに、ほぼ自動的にサービス化まで実現することができます。つまり、良質の学習データが集まれば人手を介さず自動で精度が上がってくれるAIらしい自己学習するサービスになるのではないでしょうか。
 今回の検証の残念な点は、Seldon Coreが提供するサンプルをベースに検証を行ったため、学習済みモデルをコンテナの中にいれてビルドした点です。これにより、モデルが巨大化するに伴いコンテナのサイズが大きくなってしまいます。今回は特にふれませんでしたが、永続化ボリュームを、うまく使うことでコンテナのサイズが肥大化することを抑えることができます。興味のある方は、試してみては如何でしょうか。

参考情報


このエントリは、弊社 Z Lab のメンバーによる Z Lab Advent Calendar 2018 の6日目として業務時間中に書きました。7日目は @inajob の担当です。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away