はじめに
機械学習(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のチュートリアルなどで、お馴染みの最急降下法を使った機械学習です。
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_graph
とsaver.restore
で、学習済みの機械学習のモデルを読み込みます。
predict
メソッドが、REST APIで呼び出された際に実行されるメソッドになります。
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のパッケージを指定します。
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
)を示します。
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ファイルを作ってしまえば、精度をあげていくのは、以下の作業の繰り返しになります。
- 新しいデータを使って学習しモデル生成。
- s2iコマンドを使ってコンテナイメージをBuild&Push
- Kubernetesへデプロイ
このような繰り返しの作業は、CI/CDに組み込むことで、新たなデータで学習を行うたびに、ほぼ自動的にサービス化まで実現することができます。つまり、良質の学習データが集まれば人手を介さず自動で精度が上がってくれるAIらしい自己学習するサービスになるのではないでしょうか。
今回の検証の残念な点は、Seldon Coreが提供するサンプルをベースに検証を行ったため、学習済みモデルをコンテナの中にいれてビルドした点です。これにより、モデルが巨大化するに伴いコンテナのサイズが大きくなってしまいます。今回は特にふれませんでしたが、永続化ボリュームを、うまく使うことでコンテナのサイズが肥大化することを抑えることができます。興味のある方は、試してみては如何でしょうか。
参考情報
このエントリは、弊社 Z Lab のメンバーによる Z Lab Advent Calendar 2018 の6日目として業務時間中に書きました。7日目は @inajob の担当です。