Cloud FunctionsとCloud ML Engineを連携させてみた

## 概要

Cloud Functionsを利用してCloud ML Engineのジョブ登録をしてみました。


## 前提

### Cloud FunctionsはPython 3.7(Beta)を利用

Cloud FunctionsでPython？という方は下記をご参考ください。

Google Cloud FunctionsでPythonを利用してみた(Beta利用)
https://qiita.com/kai_kou/items/dca21cdfd8375a247c2f


### Cloud ML Engineのジョブ登録

下記記事の手順で、```gcloud ml-engine jobs submit training```が実行できる環境がある前提です。

MacでCloud Machine Learning Engineを利用してみる
https://qiita.com/kai_kou/items/0952c9b28efe19f2f680


## 手順

### 下準備

変数を設定します。上記記事の手順を済ませている場合、すでに設定済みだと思います。
設定済みの場合、ジョブ名は重複するとCloud MLでジョブ登録できないので、変更しましょう。

```sh:fish
> set -x REGION us-central1
> set -x BUCKET_NAME 任意のバケット名

> set -x TRAIN_DATA gs://$BUCKET_NAME/data/adult.data.csv
> set -x EVAL_DATA gs://$BUCKET_NAME/data/adult.test.csv
> set -x TEST_JSON gs://$BUCKET_NAME/data/test.json
> set -x JOB_NAME functions_ml_test_01
```

```sh:bach
> export REGION=us-central1
> export BUCKET_NAME=任意のバケット名

> export TRAIN_DATA=gs://$BUCKET_NAME/data/adult.data.csv
> export EVAL_DATA=gs://$BUCKET_NAME/data/adult.test.csv
> export TEST_JSON=gs://$BUCKET_NAME/data/test.json
> export JOB_NAME=functions_ml_test_01
```


データをCloud Storageにコピーします。
上記記事の手順を済ませている場合、すでにファイルはコピーされていると思います。

```sh
> cd 任意のディレクトリ/cloudml-samples/census/estimator/

> gsutil cp -r data/ gs://$BUCKET_NAME/data/

> gsutil ls gs://$BUCKET_NAME/data/
gs://任意のバケット名/data/adult.data.csv
gs://任意のバケット名/data/test.json
```


Cloud ML Engineにジョブ登録するのに利用するPythonパッケージを作成します。

```sh
> touch setup.py
> touch requirements.txt
```

```python:setup.py
#!/usr/bin/env python

from setuptools import setup, Command, find_packages


with open('requirements.txt') as f:
    required = f.read().splitlines()

setup(name='functions_ml_test',
      version='0.0.1',
      description='hoge',
      license='Apache License 2.0',
      author='hoge',
      author_email='hoge@example.com',
      url='',
      packages=find_packages(),
      install_requires = required
     )
```

```txt:requirements.txt
tensorflow==1.7.1
```

パッケージ作成に必要なファイルを用意したら、パッケージングします。

```sh
> python setup.py sdist
(略)
Writing functions_ml_test-0.0.1/setup.cfg
Creating tar archive
removing 'functions_ml_test-0.0.1' (and everything under it)

> ls dist/
functions_ml_test-0.0.1.tar.gz
```

パッケージングできたらCloud Storageへコピーします。

```sh
> gsutil cp dist/functions_ml_test-0.0.1.tar.gz gs://$BUCKET_NAME/sources

> gsutil ls gs://$BUCKET_NAME/sources
gs://任意のバケット名/sources/functions_ml_test-0.0.1.tar.gz
```


### ローカルでMLジョブ登録できるか確認

ローカルからCloud MLにジョブ登録できるか確認しておきます。

```sh
> gcloud ml-engine jobs submit training $JOB_NAME \
    --job-dir gs://$BUCKET_NAME/$JOB_NAME \
    --runtime-version 1.8 \
    --python-version=3.5 \
    --module-name trainer.task \
    --packages gs://$BUCKET_NAME/sources/functions_ml_test-0.0.1.tar.gz \
    --region $REGION \
    -- \
    --train-files $TRAIN_DATA \
    --eval-files $EVAL_DATA \
    --train-steps 1000 \
    --eval-steps 100 \
    --verbosity DEBUG

Job [functions_ml_test_01] submitted successfully.
Your job is still active. You may view the status of your job with the command

  $ gcloud ml-engine jobs describe functions_ml_test_01

or continue streaming the logs with the command

  $ gcloud ml-engine jobs stream-logs functions_ml_test_01
jobId: functions_ml_test_01
state: QUEUED
```

ジョブ登録後、学習が完了したか確認します。

```sh
> gcloud ml-engine jobs describe $JOB_NAME

createTime: '2018-08-07T08:14:01Z'
endTime: '2018-08-07T08:21:05Z'
etag: SPDifLtZPoY=
jobId: functions_ml_test_01
startTime: '2018-08-07T08:14:26Z'
state: SUCCEEDED
(略)
```
はい。

### Cloud Functionsで関数登録

ここからが本題です。
Cloud Functionsで関数を作成します。ここではローカルからデプロイしてみます。
ランタイムはPython 3.7なので、Cloud FunctionsでPython？という方は下記をご参考ください(再掲

Google Cloud FunctionsでPythonを利用してみた(Beta利用)
https://qiita.com/kai_kou/items/dca21cdfd8375a247c2f

#### ソースの準備

先程のディレクトリとは別ディレクトリで作業します。

```sh
> mkdir 任意のディレクトリ/function_ml_test
> cd 任意のディレクトリ/function_ml_test
> touch main.py
> touch requirements.txt
```

とりあえずジョブ登録したいだけなので、パラメータはべた書きにしてます。(雑
実際に使うときには環境変数や実行時にパラメータ指定できるようにしましょう。

```任意のバケット名```、```GCPのプロジェクトID``` の置き換え忘れにご注意(1敗
ジョブID(ここでは```functions_ml_test_02```)は先程ローカルからジョブ登録した際のものと変えておきます。

```python:main.py
from googleapiclient import discovery

def create_ml_job(request):
    # パラメータ設定
    training_inputs = {
        'packageUris': ['gs://任意のバケット名/sources/functions_ml_test-0.0.1.tar.gz'],
        'pythonModule': 'trainer.task',
        'args': [
            '--job-dir', 'gs://任意のバケット名/functions_ml_test_02',
            '--train-files', 'gs://任意のバケット名/data/adult.data.csv',
            '--eval-files', 'gs://任意のバケット名/data/adult.test.csv',
            '--train-steps', 1000,
            '--eval-steps', 100,
            '--verbosity', 'DEBUG',
        ],
        'region': 'us-central1',
        'runtimeVersion': '1.8',
        'pythonVersion': '3.5'
    }
    job_spec = {'jobId': 'functions_ml_test_02', 'trainingInput': training_inputs}

    # Cloud MLにジョブ登録
    project_id = 'projects/{}'.format('GCPのプロジェクトID')
    cloudml = cloudml = discovery.build('ml', 'v1')
    request = cloudml.projects().jobs().create(body=job_spec,
                parent=project_id)
    response = request.execute()
```

Cloud MLにジョブ登録するのに、Python用のライブラリを利用するので、```requirements.txt```に指定しておきます。

```txt:requirements.txt
google-api-python-client
```

#### Cloud Functionsへデプロイ

ローカルで用意したソースをデプロイします。

```--trigger-xxx```は必須となるので、ここでは```--trigger-http``` としておきます。
```--runtime=python37```の指定がないとNode.jsと扱いされるのでご注意ください。
あとは、```gcloud functions```ではなくて、```gcloud beta functions``` です。

```sh
> gcloud beta functions deploy create_ml_job --trigger-http --runtime=python37

Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
entryPoint: create_ml_job
(略)
name: projects/xxx/locations/us-central1/functions/create_ml_job
runtime: python37
(略)
status: ACTIVE
timeout: 60s
updateTime: '2018-08-07T08:55:01Z'
versionId: '1'
```
はい。

デプロイできたら実行してみます。

```sh
> gcloud beta functions call create_ml_job

executionId: r8br62c1rk9g
result: OK
```

Cloud MLにジョブ登録できたので、学習完了を見守りましょう。

```sh
> gcloud ml-engine jobs describe functions_ml_test_02

createTime: '2018-08-07T09:34:20Z'
endTime: '2018-08-07T09:40:53Z'
etag: OJrb-vhEags=
jobId: functions_ml_test_02
startTime: '2018-08-07T09:34:46Z'
state: SUCCEEDED
(略)
```
はい。

無事に登録＆実行できました。
とりあえず連携できるよね。って実装なので、各パラメータは環境変数に入れるか実行時に指定できるようにしましょう。(2回目)

## 参考

Google Cloud FunctionsでPythonを利用してみた(Beta利用)
https://qiita.com/kai_kou/items/dca21cdfd8375a247c2f

MacでCloud Machine Learning Engineを利用してみる
https://qiita.com/kai_kou/items/0952c9b28efe19f2f680