Google AI Platform - Cloud ML Engineを初心者が動かして理解(前半)

[入門ガイド](https://cloud.google.com/ml-engine/docs/tensorflow/how-tos)を見ながらCloud ML Engineでカスタムプログラムを動かしてみました。Cloud ML Engineを理解するためにしているので、print文を処理するだけの最小構成にしています。もう少し複雑にした[「後半」](https://qiita.com/FukuharaYohei/items/a4621e0a80063047e340)記事も書きました。
[公式チュートリアル](https://cloud.google.com/ml-engine/docs/tensorflow/getting-started-training-prediction)実行に関しては、記事[「Google Cloud ML EngineでTensorFlow機械学習訓練実行」](https://qiita.com/FukuharaYohei/items/2a435ef83ecf1702395c)を参照ください。
ファイルの読み書きをしたい場合には、記事[「Google AI Platform - Cloud ML EngineとファイルI/O」](https://qiita.com/FukuharaYohei/items/56bbd3843fa791bfb7dd)を参照ください。

# 環境
確認していないのですが、クラウド側で処理しているのでPythonなくても動かせそうな気がします。

|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu18.04.01 LTS|仮想で動かしています|
|Google Cloud SDK|250.0.0||

# 前提

- GCPのアカウントを持っていること
- GCPでプロジェクトを作成済でAPI有効化であること(今回は"aip-test01"というIDで作成しています)
- Google Cloud SDKがインストールされていること

※GCPでのプロジェクト作成、API有効化とGoogle Cloud SDKインストールに関しては[「過去記事」](https://qiita.com/FukuharaYohei/items/2a435ef83ecf1702395c#1-gcp-%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E8%A8%AD%E5%AE%9A)に記載しています。


# 手順
## 1. Google Cloud SDK設定

```bash
# 設定確認
gcloud config list

# プロジェクトID設定(ここはその時にIDに置き換える)
gcloud config set project aip-test01
```

Google Cloud SDKを最新にします。

```bash
sudo apt-get update && sudo apt-get --only-upgrade install kubectl google-cloud-sdk google-cloud-sdk-app-engine-grpc google-cloud-sdk-pubsub-emulator google-cloud-sdk-app-engine-go google-cloud-sdk-cloud-build-local google-cloud-sdk-datastore-emulator google-cloud-sdk-app-engine-python google-cloud-sdk-cbt google-cloud-sdk-bigtable-emulator google-cloud-sdk-app-engine-python-extras google-cloud-sdk-datalab google-cloud-sdk-app-engine-java
```

## 2. Pythonプログラム作成
print文だけなのでライブラリは何も使っていません。
ローカル環境はPython3.5.6にpyenvで設定しています。[Google Cloud ML Engineのサポート対象](https://cloud.google.com/ml-engine/docs/tensorflow/getting-started-training-prediction#python_version_support)はPython3.5と少し古めなので注意してください。
試したい方は、[GitHub](https://github.com/YoheiFukuhara/gcp-aip-test)からクローンしてください。

ディレクトリとプログラムは以下の構成です。ディレクトリ"trainer"は[推奨プロジェクト構造](https://cloud.google.com/ml-engine/docs/tensorflow/packaging-trainer#project-structure)に記載されていた通りにしています。
["\_\_init\_\_.py"](https://github.com/YoheiFukuhara/gcp-aip-test/blob/master/trainer/__init__.py)の中身は空です。深く調べていませんが、ディレクトリ"trainer"と"\_\_init\_\_.py"なしだと動きませんでした。

```
.
└── trainer
    ├── __init__.py
    └── task.py
```

["task.py"](https://github.com/YoheiFukuhara/gcp-aip-test/blob/master/trainer/task.py)はprint文を書いているだけです。

```python
print("Hello World!!")
```

## 3. ローカル試行
このステップは必須ではないです。実行する場合には、ローカル環境にPythonやライブラリの準備が必要です。
まずはローカルで試行します。

```bash
# from project root directory
$ gcloud ai-platform local train \
>    --module-name trainer.task \
>    --package-path trainer/
Hello World!!
```

Hello World!!が出力されました。"modle-name"に実行するpythonプログラムを指定し、"package-path"はプログラムが格納されているフォルダを指定します。クラウドでの実行時には、"package-path"で指定したフォルダを圧縮してGoogle Storageに格納するようです。

## 4. クラウドで実行
### 4.1. 前準備
環境変数設定やバケット作成をしておきます。

#### 4.1.1. ジョブ名
ジョブ名に日時を変数指定します。ジョブ名は実行後に結果確認するときに使うので日時を加えると便利です。

```bash
now=$(date +"%Y%m%d_%H%M%S")
JOB_NAME="aip_test_$now"
```

#### 4.1.2. Google Storageバケット
Google Storageのバケット作成と環境変数作成をします。

```bash
# Create a Bucket
BUCKET_NAME="aip-test01"
gsutil mb gs://$BUCKET_NAME/
```

OUTPUT_PATHとして、パッケージ全体を圧縮して置くパスをGoogle Storageに設定します。

```bash
# Set output path
OUTPUT_PATH=gs://$BUCKET_NAME/$JOB_NAME
```

### 4.2. ジョブのクラウド実行
ジョブをクラウドで実行します。使用可能なパラメータは["Configuring the job"](https://cloud.google.com/ml-engine/docs/tensorflow/training-jobs#configuring_the_job)で確認できます。

```bash
$ gcloud ai-platform jobs submit training $JOB_NAME \
>     --job-dir $OUTPUT_PATH \
>     --module-name trainer.task \
>     --package-path trainer/ 

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

  $ gcloud ai-platform jobs describe aip_test_20190615_173337

or continue streaming the logs with the command

  $ gcloud ai-platform jobs stream-logs aip_test_20190615_173337
jobId: aip_test_20190615_173337
```

#### 注意すべきパラメータ
以下のパラメータには注意してください。今回は、単純なプログラムなのでパラメータ指定をしないでもエラーが起きませんが、現実的には指定が必須かと思います。また、[使用可能なRuntime Version](https://cloud.google.com/ml-engine/docs/tensorflow/runtime-version-list)の日本語ページは古いことがあったので、英語で見ることをおすすめします。

|パラメータ|内容|注意点|
|:-:|:-:|:-:|
|python-version|Pythonバージョン|デフォルトのpythonバージョンは2.7なので注意してください([Python Version](https://cloud.google.com/ml-engine/docs/tensorflow/versioning#set-python-version-training))|
|runtime-version|ランタイム|デフォルトのランタイムは古いので注意してください([Runtime](https://cloud.google.com/ml-engine/docs/tensorflow/runtime-version-list))|

[「後半記事」](https://qiita.com/FukuharaYohei/items/a4621e0a80063047e340#%E6%89%8B%E9%A0%86)では上記パラメータを指定して実行しています。

### 4.3. ジョブ実行結果確認
実行したジョブ結果を確認します。ブラウザからコンソールで確認してもOKですが、今回はターミナルから確認します。
まだ、ステータスが実行中(RUNNING)となっていますが、print文だけなのですぐに終わります。

```bash
$ gcloud ai-platform jobs describe aip_test_20190615_173337
createTime: '2019-06-15T08:47:23Z'
etag: 0jJmjgUxgzk=
jobId: aip_test_20190615_173337
startTime: '2019-06-15T08:48:16Z'
state: RUNNING
trainingInput:
  jobDir: gs://aip-test01/aip_test_20190615_173337
  packageUris:
  - gs://aip-test01/aip_test_20190615_173337/packages/6a16ee7a0297ae067db3aaed13ce1e4947e31010d175eada71eaf1bcf613be85/trainer-0.0.0.tar.gz
  pythonModule: trainer.task
  region: us-central1
trainingOutput: {}

View job in the Cloud Console at:
https://console.cloud.google.com/mlengine/jobs/aip_test_20190615_173337?project=aip-test01

View logs at:
https://console.cloud.google.com/logs?resource=ml.googleapis.com%2Fjob_id%2Faip_test_20190615_173337&project=aip-test0
```

ジョブのログも確認します。後ろの方に"Hello World!!"と出力されているのがわかります。

```bash
$ gcloud ai-platform jobs stream-logs aip_test_20190615_173337
INFO	2019-06-15 17:47:23 +0900	service		Validating job requirements...
INFO	2019-06-15 17:47:23 +0900	service		Job creation request has been successfully validated.
INFO	2019-06-15 17:47:23 +0900	service		Job aip_test_20190615_173337 is queued.
INFO	2019-06-15 17:47:24 +0900	service		Waiting for job to be provisioned.
INFO	2019-06-15 17:47:26 +0900	service		Waiting for training program to start.
INFO	2019-06-15 17:48:09 +0900	master-replica-0		Running task with arguments: --cluster={"master": ["127.0.0.1:2222"]} --task={"type": "master", "index": 0} --job={  "package_uris": ["gs://aip-test01/aip_test_20190615_173337/packages/6a16ee7a0297ae067db3aaed13ce1e4947e31010d175eada71eaf1bcf613be85/trainer-0.0.0.tar.gz"],  "python_module": "trainer.task",  "region": "us-central1",  "job_dir": "gs://aip-test01/aip_test_20190615_173337",  "run_on_raw_vm": true}
INFO	2019-06-15 17:48:35 +0900	master-replica-0		Running module trainer.task.
INFO	2019-06-15 17:48:35 +0900	master-replica-0		Downloading the package: gs://aip-test01/aip_test_20190615_173337/packages/6a16ee7a0297ae067db3aaed13ce1e4947e31010d175eada71eaf1bcf613be85/trainer-0.0.0.tar.gz
INFO	2019-06-15 17:48:35 +0900	master-replica-0		Running command: gsutil -q cp gs://aip-test01/aip_test_20190615_173337/packages/6a16ee7a0297ae067db3aaed13ce1e4947e31010d175eada71eaf1bcf613be85/trainer-0.0.0.tar.gz trainer-0.0.0.tar.gz
INFO	2019-06-15 17:48:37 +0900	master-replica-0		Installing the package: gs://aip-test01/aip_test_20190615_173337/packages/6a16ee7a0297ae067db3aaed13ce1e4947e31010d175eada71eaf1bcf613be85/trainer-0.0.0.tar.gz
INFO	2019-06-15 17:48:37 +0900	master-replica-0		Running command: pip install --user --upgrade --force-reinstall --no-deps trainer-0.0.0.tar.gz
ERROR	2019-06-15 17:48:42 +0900	master-replica-0		DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7.
INFO	2019-06-15 17:48:42 +0900	master-replica-0		Processing ./trainer-0.0.0.tar.gz
INFO	2019-06-15 17:48:42 +0900	master-replica-0		Building wheels for collected packages: trainer
INFO	2019-06-15 17:48:42 +0900	master-replica-0		  Building wheel for trainer (setup.py): started
INFO	2019-06-15 17:48:43 +0900	master-replica-0		creating '/tmp/pip-wheel-NgW3Y5/trainer-0.0.0-cp27-none-any.whl' and adding '.' to it
INFO	2019-06-15 17:48:43 +0900	master-replica-0		adding 'trainer/task.py'
INFO	2019-06-15 17:48:43 +0900	master-replica-0		adding 'trainer/__init__.py'
INFO	2019-06-15 17:48:43 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/DESCRIPTION.rst'
INFO	2019-06-15 17:48:43 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/metadata.json'
INFO	2019-06-15 17:48:43 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/top_level.txt'
INFO	2019-06-15 17:48:43 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/WHEEL'
INFO	2019-06-15 17:48:43 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/METADATA'
INFO	2019-06-15 17:48:43 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/RECORD'
INFO	2019-06-15 17:48:43 +0900	master-replica-0		  Building wheel for trainer (setup.py): finished with status 'done'
INFO	2019-06-15 17:48:43 +0900	master-replica-0		  Stored in directory: /root/.cache/pip/wheels/0d/1b/db/f8e86b296734f0b137e17e5d34862f4ae4faf8388755c6272f
INFO	2019-06-15 17:48:43 +0900	master-replica-0		Successfully built trainer
INFO	2019-06-15 17:48:43 +0900	master-replica-0		Installing collected packages: trainer
INFO	2019-06-15 17:48:43 +0900	master-replica-0		Successfully installed trainer-0.0.0
INFO	2019-06-15 17:48:43 +0900	master-replica-0		Running command: pip install --user trainer-0.0.0.tar.gz
ERROR	2019-06-15 17:48:43 +0900	master-replica-0		DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7.
INFO	2019-06-15 17:48:43 +0900	master-replica-0		Processing ./trainer-0.0.0.tar.gz
INFO	2019-06-15 17:48:43 +0900	master-replica-0		Building wheels for collected packages: trainer
INFO	2019-06-15 17:48:43 +0900	master-replica-0		  Building wheel for trainer (setup.py): started
INFO	2019-06-15 17:48:44 +0900	master-replica-0		creating '/tmp/pip-wheel-GJXs7n/trainer-0.0.0-cp27-none-any.whl' and adding '.' to it
INFO	2019-06-15 17:48:44 +0900	master-replica-0		adding 'trainer/task.py'
INFO	2019-06-15 17:48:44 +0900	master-replica-0		adding 'trainer/__init__.py'
INFO	2019-06-15 17:48:44 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/DESCRIPTION.rst'
INFO	2019-06-15 17:48:44 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/metadata.json'
INFO	2019-06-15 17:48:44 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/top_level.txt'
INFO	2019-06-15 17:48:44 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/WHEEL'
INFO	2019-06-15 17:48:44 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/METADATA'
INFO	2019-06-15 17:48:44 +0900	master-replica-0		adding 'trainer-0.0.0.dist-info/RECORD'
INFO	2019-06-15 17:48:44 +0900	master-replica-0		  Building wheel for trainer (setup.py): finished with status 'done'
INFO	2019-06-15 17:48:44 +0900	master-replica-0		  Stored in directory: /root/.cache/pip/wheels/0d/1b/db/f8e86b296734f0b137e17e5d34862f4ae4faf8388755c6272f
INFO	2019-06-15 17:48:44 +0900	master-replica-0		Successfully built trainer
INFO	2019-06-15 17:48:44 +0900	master-replica-0		Installing collected packages: trainer
INFO	2019-06-15 17:48:44 +0900	master-replica-0		  Found existing installation: trainer 0.0.0
INFO	2019-06-15 17:48:44 +0900	master-replica-0		    Uninstalling trainer-0.0.0:
INFO	2019-06-15 17:48:44 +0900	master-replica-0		      Successfully uninstalled trainer-0.0.0
INFO	2019-06-15 17:48:44 +0900	master-replica-0		Successfully installed trainer-0.0.0
INFO	2019-06-15 17:48:44 +0900	master-replica-0		Running command: python -m trainer.task --job-dir gs://aip-test01/aip_test_20190615_173337
INFO	2019-06-15 17:48:45 +0900	master-replica-0		Hello World!!
INFO	2019-06-15 17:48:45 +0900	master-replica-0		Module completed; cleaning up.
INFO	2019-06-15 17:48:45 +0900	master-replica-0		Clean up finished.
INFO	2019-06-15 17:48:45 +0900	master-replica-0		Task completed successfully.
INFO	2019-06-15 17:53:51 +0900	service		Job completed successfully.
```

ちなみに、Google Storage(実行時にパラメータ"job-dir"で指定したパス)に出力されたパッケージの内容も確認してみます。tar.gzの圧縮されたファイルがひとつだけあるのがわかります。

```bash
$ gsutil ls gs://aip-test01/**
gs://aip-test01/aip_test_20190615_173337/packages/6a16ee7a0297ae067db3aaed13ce1e4947e31010d175eada71eaf1bcf613be85/trainer-0.0.0.tar.gz
```

圧縮ファイルの中身を確認すると以下の内容が詰まっていました。

```
.
└── trainer-0.0.0
    ├── PKG-INFO
    ├── setup.py
    └── trainer
        ├── __init__.py
        └── task.py
```