【強化学習】クラウドサービスを利用した分散強化学習（GKE/有料編）


この記事は自作している[強化学習フレームワーク](https://qiita.com/pocokhc/items/a2f1ba993c79fdbd4b4d)の解説記事です。

https://github.com/pocokhc/simple_distributed_rl

この記事のコード場所：[examples/kubernetes](https://github.com/pocokhc/simple_distributed_rl/tree/main/examples/kubernetes
) 

続きです。
前回作成したk8s環境をGKEに作成します。

※有料サービスを取り扱うので利用する場合は自己責任でお願いします

1：[【強化学習】クラウドサービスを利用した分散強化学習（無料編）](https://qiita.com/pocokhc/items/f7a32ee6c62cba54d6ab)
2：[【強化学習】クラウドサービスを利用した分散強化学習（kubernetes編）](https://qiita.com/pocokhc/items/56c930e1e401ce156141)
3：ここ

# 全体イメージ(GKE)

今回作成する構成の全体イメージは以下です。

![a-ページ8.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/396150/e9d49a8a-16c9-0997-d558-5dc058b32185.png)

前回との違いは Trainer と Redis を同じPodに入れています。
Trainer と Queue は遅延をなるべく減らしたかったので同じ物理サーバにアサインされるように同じPodにいれました。

# Google Kubernetes Engine (GKE)

GKEはGoogle Cloud Platform(GCP)のサービスで Kubernetes をクラウド上に構築してくれるサービスです。
GCPも初めて触るので寛大な目で見てください…。

## 料金体系

今回GCPで使うサービスは「Artifact Registry」と「Kubernetes Engine」です。

### Artifact Registry

Dockerイメージを管理するサービスのようで、Container Registry が非推奨になり、その後継サービスとなります。

料金については以下。
https://cloud.google.com/artifact-registry/pricing?hl=ja

ざっくり言うと、アップロードは無料で、ダウンロードは同じロケーションなら無料のようです。
また、容量ですが0.5GB以上使用すると 0.1$ GB/月 かかるようです。

今回作成したイメージは約3.6GBほどになります。（"tensorflow/tensorflow:2.14.0-gpu" だけで3.5GBあります）
なので消さずに放置していると月54円ほど（1$=150円）料金が発生する計算です。

### Kubernetes Engine

k8sをメインで動かすサービスですが、料金体系もかなり複雑です…。

料金ページは以下で基本は Compute Engine サービスの課金がメインになります。
https://cloud.google.com/kubernetes-engine/pricing?hl=ja
https://cloud.google.com/compute/all-pricing?hl=ja

今回はStandardモードを使います。

ざっくりのイメージですが、CPU/メモリ/NW/ディスク等のリソースに対して課金されるようです。
（プリエンプティブルVMは名称が変わってSpotVMになっています）

課金の感覚は、固定額が1時間1クラスタあたり＄0.10、1CPU 0.04＄/h、1GPU 0.45＄/h、メモリ1GBあたり0.004＄/h、SpotVMだと半額ほど?になります。
今回例えば1actor+1trainerで2CPU+1GPU+メモリ16GBだと仮定した場合、約0.6＄/h≒90円/h（1$=150円）でしょうか。（SporVMだと45円/h）

今回実際にかかった金額は記事の最後の方でまとめたいと思います。


## 1. 準備

GCPのページは以下です。

GCP : https://console.cloud.google.com/?hl=ja
Kubernetes Engine : https://cloud.google.com/kubernetes-engine?hl=ja

利用するにはGoogleアカウントの登録とクレジットカードの登録が必要です。
有料サービスですが初回は無料枠があるようで、今回いろいろ試しましたがまだ無料枠に収まっていました。

GCPのブラウザ画面からプロジェクトを作成した後、左メニューから "Artifact Registry" と "Kubernetes Engine" を選びAPIを有効化しておきます。

また、以下から gcloud も入れておきます。

https://cloud.google.com/sdk/docs/install?hl=ja


## 2-0. コンテナイメージの作成

前回と同じ内容ですので作成済みの人は読み飛ばして大丈夫です。

以下のファイルを作成します。

```
./
 ├ docker_desktop/
 |  └ redis.yaml
 |
 ├ dockerfile        # new
 ├ server_actor.py   # new
 └ server_trainer.py # new
```

``` python: ./server_actor.py
from srl.runner.distribution import RedisParameters, actor_run_forever
from srl.utils.common import logger_print

logger_print()
actor_run_forever(RedisParameters(host="redis-internal-service"), None)
```

``` python: ./server_trainer.py
from srl.runner.distribution import RedisParameters, trainer_run_forever
from srl.utils.common import logger_print

logger_print()
trainer_run_forever(RedisParameters(host="redis-internal-service"),None)
```

``` dockerfile: ./dockerfile
# syntax=docker/dockerfile:1

# --- select image CPU(1.76GB) or GPU(7.38GB)
FROM tensorflow/tensorflow:2.14.0-gpu
#FROM tensorflow/tensorflow:2.14.0

WORKDIR /code
RUN apt-get update \
 && apt install -y --no-install-recommends git \
 && apt-get install -y --no-install-recommends libgl1-mesa-dev libglib2.0-0 \
 && rm -rf /var/lib/apt/lists/* \
 && pip install --no-cache-dir git+https://github.com/pocokhc/simple_distributed_rl@v0.13.3 \
# 必要なライブラリを適宜入れる
 && pip install --no-cache-dir opencv-python pygame gymnasium redis async_timeout

# エントリーポイントのファイルをコピー
COPY server_*.py /code/

```

イメージ化のコマンド(最後の.も必要)

``` sh
> docker build --pull --rm -t srl_qiita:latest .
```


## 2-1. Artifact Registryへの登録

GCPブラウザ画面左メニューから "Artifact Registry" → "リポジトリ" → "リポジトリを作成" を選択します。
設定しないといけない項目は名前とリージョンぐらいでしょうか。

リージョンは "Artifact Registry" と "Kubernetes Engine" でなるべく同じリージョンを選択します。
ロケーションが大きく違うと別途料金がかかるようです。（例えば米国からカナダだと$0.01）

リージョン以外の設定はデフォルトで作成しました。
作成したらローカルPCからPowerShellを起動し、以下コマンドを実行します。

``` sh
# 最初だけ、ブラウザが開き認証が求められる
> gcloud auth login

# 最初だけ、リージョンへのアクセス認証する
# [リージョンアドレス] はブラウザのリポジトリで表示されている
# [リージョンアドレス] > [プロジェクトID] > [リポジトリ名]
# （"us-west1" を選んだ場合だと "us-west1-docker.pkg.dev"）
> gcloud auth configure-docker [リージョンアドレス]

# 2-0で作成したイメージにタグをつける、以下のtagを付ける必要があるらしい
# [リージョンアドレス]/[プロジェクトID]/[リポジトリ名]/[イメージ名]:[tag名]
> docker tag srl_qiita:latest [リージョンアドレス]/[プロジェクトID]/[リポジトリ名]/srl_qiita:latest

# イメージをアップロード
> docker push [リージョンアドレス]/[プロジェクトID]/[リポジトリ名]/srl_qiita:latest
```


## 3. Kubernetes Engine

※クラスタを作成すると課金が発生します

まずはクラスタを作成します。
GCPブラウザ画面左メニューから "Kubernetes Engine" → "クラスタ" → "作成" を選択します。

AutopilotとStandardがありますが、Standardクラスタを作成します。
（最初はAutopilotモードも試していましたが、作ってはすぐ捨てる今回のようなサービスでは相性があまりよくなさそうでした…）
（各Podの役割も明確でリソースも決まっているのでスケーリングで再作成されると逆に困る）

クラスタで決める必要があるのはノードプールで、クラスタはこれに従って実際のノードがアサインされます。（と思っています…）
ノードプール作成の参考例は以下です。（素人なのであまり最適なつくり方が分かっていません）

+ ノードプール1
   + 役割：コントロールプレーン/Actor用
   + ノード数：Actor数
   + マシンの構成：汎用
   + 備考：Actor割り当て用なので1CPU=1ノードのイメージ。CPUだけあればマシンスペックはそこまでいりません。（余裕があれば早いCPUにしてもいいですが、そこまで重要ではない）
+ ノードプール2
   + 役割：Trainer用
   + ノード数：1
   + マシンの構成：GPU  ※初期設定ではGPUはアサインできない（後述）
   + 備考：学習特化なのでGPU(またはCPU)の速度特化。また、Actorで収集されたbatchはこのノードが受け持つのでメモリサイズも結構ほしい。

また、今回DBは扱っていないのでディスクはいりません。

実際のノードのアサインはGoogle側のリソースの利用状況で変化し、アサインする物理ノードがない場合は失敗したりします。
SpotVMを有効にするとアサインが成功しやすいイメージです。


### GPU

・GPU制限の解除

要求できるリソースですが、初期設定では制限がかかっているようでGPU数は0でした。
リソースは以下で確認出来ます。

ブラウザの左メニュー → "IAMと管理" → "割り当て"
フィルタで "GPUs (all regions)" を指定。

この項目を編集して割り当てを1にしてリクエストを送信します。
1への変更は自動認証っぽくてすぐ認証されました。

割り当てのドキュメント：https://cloud.google.com/compute/resource-usage?hl=ja


・クラスタノードでGPUが使えるノードをアサインする

リージョンによって使えるGPUが変わります。
以下を参考
https://cloud.google.com/compute/docs/gpus/gpu-regions-zones?hl=ja

・GPU対応コンテナを使う

今回使ってるベースイメージが "tensorflow/tensorflow:2.14.0-gpu" なので対応しています。
以下コンテナ内での確認用コマンドです。

``` sh
# OSが認識しているか
> nvidia-smi

# tensorflowが認識しているか
> python -c "from tensorflow.python.client import device_lib; print(device_lib.list_local_devices())"
```


・マニフェストファイルの変更

この後に記載がありますが、GPUを使う場合はマニフェストファイルを変更する必要があります。
GPUに関係ある個所は以下です。

``` yaml
spec:
  # nodeSelectorで使うGPUのタイプ選択
  nodeSelector:
    cloud.google.com/gke-accelerator: "nvidia-tesla-t4"

  # コンテナ内のリソースでGPUを指定
  containers:
    - name: trainer-node
    resources:
      requests:
        nvidia.com/gpu: "1"
```

使えるGPUのタイプは以下から確認できます。
https://cloud.google.com/kubernetes-engine/docs/how-to/gpus?hl=ja


## 4. GKEへの接続

クラスタが無事にできたら以下のコマンドでローカルから接続します。
（ブラウザから作成したクラスタを見ると、「接続」ボタンがあるので、それを押すとコマンドが表示されます）

```
> gcloud container clusters get-credentials [クラスタ名] --region [リージョン] --project [プロジェクト名]

# ※contextが変わります
> kubectl config get-contexts
CURRENT   NAME             CLUSTER         AUTHINFO        NAMESPACE
          docker-desktop   docker-desktop  docker-desktop
*         gke_[プロジェクト名]_[リージョン]_[クラスタ名]
```

## 5. GKE用のマニフェストファイルの作成

GKE用のマニフェストファイルを作成します。
ほとんど同じですがリソース等を変えています。

```
./
 ├ docker_desktop/
 |  ├ redis.yaml
 |  ├ actor.yaml
 |  └ trainer.yaml
 |
 ├ gke/             # new
 |  ├ service.yaml  # new
 |  ├ actor.yaml    # new
 |  └ trainer-redis.yaml  # new
 |
 ├ dockerfile
 ├ server_actor.py
 └ server_trainer.py
```

actor.yaml の image は「Artifact Registry」で登録したimageを指定します。

``` yaml: ./gke/actor.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: actor
spec:
  replicas: 1  # actor_num
  selector:
    matchLabels:
      app: actor
  template:
    metadata:
      labels:
        app: actor
    spec:
      containers:
        - name: actor-node
          image: [リージョンアドレス]/[プロジェクトID]/[リポジトリ名]/srl_qiita:latest
          #command: ["sh", "-c", "while true; do sleep 3600; done"]
          command: ["python", "/code/server_actor.py"]
          resources:
            requests:
              cpu: "10m"
              memory: "256Mi"
            limits:
              cpu: "1"
              memory: "1Gi"

```

trainer-redis.yaml は Trainer と Redis を混ぜて書いています。
Trainer の image も「Artifact Registry」で登録したimageを指定します。
resourcesのmemoryサイズはノードプールの設定に合わせて見直した方がいいかもしれません。

``` yaml: ./gke/trainer-redis.yaml
# --- Redis internal service
apiVersion: v1
kind: Service
metadata:
  name: redis-internal-service
spec:
  type: ClusterIP
  selector:
    app: trainer
  ports:
  - name: redis-internal-port
    protocol: TCP
    port: 6379
    targetPort: 6379

# --- Redis config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config
data:
  redis.conf: |-
    bind 0.0.0.0
    port 6379

---
apiVersion: v1
kind: Pod
metadata:
  name: trainer-pod
  labels:
    app: trainer
spec:
  nodeSelector:
    cloud.google.com/gke-accelerator: "nvidia-tesla-t4"
  containers:
    # --- trainer ---
    - name: trainer-node
      image: [リージョンアドレス]/[プロジェクトID]/[リポジトリ名]/srl_qiita:latest
      #command: ["sh", "-c", "while true; do sleep 3600; done"]
      command: ["python", "-u", "/code/server_trainer.py"]
      resources:
        requests:
          cpu: "10m"
          memory: "8Gi"
          nvidia.com/gpu: "1"
        limits:
          cpu: "1"
          memory: "12Gi"
          nvidia.com/gpu: "1"

    # --- Redis ---
    - name: redis-server
      image: redis:7.2-alpine
      ports:
        - containerPort: 6379
      command: ["redis-server", "/etc/redis/redis.conf"]
      volumeMounts:
        - name: redis-config
          mountPath: /etc/redis
      resources:
        requests:
          cpu: "10m"
          memory: "1Gi"
        limits:
          cpu: "1"
          memory: "2Gi"
      
    - name: redis-commander
      image: rediscommander/redis-commander:latest
      env: [{"name": "REDIS_HOSTS", "value": "local:localhost:6379"}]
      ports:
        - containerPort: 8081
      resources:
        requests:
          cpu: "10m"
          memory: "32Mi"
        limits:
          cpu: "200m"
          memory: "256Mi"

  volumes:
    - name: redis-config
      configMap:
        name: redis-config

```

最後に外部公開用のServiceです。
LoadBalancerはクラウドサービスを使う場合に使うTypeとなります。

``` yaml: ./gke/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  type: LoadBalancer
  selector:
    app: trainer
  ports:
  - name: redis-external-port
    protocol: TCP
    port: 16379
    targetPort: 6379
  - name: redis-web-port
    protocol: TCP
    port: 18081
    targetPort: 8081

```

できたら以下で起動します。

```
# 個別起動
> kubectl apply -f ./gke/actor.yaml
> kubectl apply -f ./gke/trainer-redis.yaml
> kubectl apply -f ./gke/service.yaml

# 一括起動
> kubectl apply -f ./gke/

# 一括終了
> kubectl delete -f ./gke/
```

起動するとブラウザの「ワークロード」でPodの状況が、「Service と Ingress」でネットワークの状況が確認できます。

「Service と Ingress」で表示されるIPアドレスですが、これは外部アクセス用のアドレスとなります。
これを知っていると誰でもアクセスできるので扱いは気を付けてください。
ポート18081の方のアドレスにアクセスし、Redis管理画面が表示されれば無事に起動しています。


## 6. 学習の実行

最後に学習です。
「Service と Ingress」で表示されたアドレスに16379ポートでアクセスします。

``` python: main.py
import srl
from srl.algorithms import dqn
from srl.runner.distribution import RedisParameters

rl_config = dqn.Config()
rl_config.hidden_block.set_mlp((64, 64))
rl_config.memory.set_proportional_memory()
rl_config.memory.capacity = 5000
runner = srl.Runner("Pendulum-v1", rl_config)

runner.train_distribution(
    RedisParameters(host="xxx.xxx.xxx.xxx", port=16379),
    actor_num=1,
    max_train_count=20_000,
)

print(runner.evaluate())
```

実行結果

``` sh
19:47:32 ACTIVE  1.66s(     - left),      0tr (-1547.3eval)
 trainer  7274b4c5-2110-40e7-bb7b-5cd606d49fcf  4.1s:     0tr/s,    0recv/s,       0tr,       0recv
 actor0   3f841208-75eb-4f89-a992-0b96796d47e5  3.1s:     0st/s,    0send/s,       0st,       0send
19:48:33 ACTIVE   1.0m(  7.2m left),   2478tr (-1480.3eval)
 trainer  7274b4c5-2110-40e7-bb7b-5cd606d49fcf  4.0s:    40tr/s,  219recv/s,    2478tr,   13410recv
 actor0   3f841208-75eb-4f89-a992-0b96796d47e5  3.0s:   221st/s,  220send/s,   13478st,   13459send
19:49:34 ACTIVE   2.1m(  4.9m left),   5490tr (-1.652eval)
 trainer  7274b4c5-2110-40e7-bb7b-5cd606d49fcf  5.1s:    49tr/s,  207recv/s,    5490tr,   26021recv
 actor0   3f841208-75eb-4f89-a992-0b96796d47e5  3.1s:   210st/s,  210send/s,   26273st,   26252send
19:50:35 ACTIVE   3.1m(  3.7m left),   8601tr (-0.665eval)
 trainer  7274b4c5-2110-40e7-bb7b-5cd606d49fcf  6.2s:    50tr/s,  145recv/s,    8601tr,   34928recv
 actor0   3f841208-75eb-4f89-a992-0b96796d47e5 13.2s:   126st/s,  126send/s,   33973st,   33964send
19:51:36 ACTIVE   4.1m(  2.7m left),  11690tr (-302.9eval)
 trainer  7274b4c5-2110-40e7-bb7b-5cd606d49fcf  6.2s:    50tr/s,  108recv/s,   11690tr,   41548recv
 actor0   3f841208-75eb-4f89-a992-0b96796d47e5 11.2s:   110st/s,  110send/s,   40703st,   40687send
19:52:37 ACTIVE   5.1m(  1.7m left),  14815tr (-134.4eval)
 trainer  7274b4c5-2110-40e7-bb7b-5cd606d49fcf  7.2s:    51tr/s,  106recv/s,   14815tr,   48029recv
 actor0   3f841208-75eb-4f89-a992-0b96796d47e5  8.2s:   112st/s,  112send/s,   47534st,   47532send
19:53:37 ACTIVE   6.1m(40.33s left),  17927tr (-126.3eval)
 trainer  7274b4c5-2110-40e7-bb7b-5cd606d49fcf  6.6s:    51tr/s,  104recv/s,   17927tr,   54382recv
 actor0   3f841208-75eb-4f89-a992-0b96796d47e5  6.6s:   108st/s,  108send/s,   54112st,   54096send
19:54:15 END   6.7m( 0.00s left),  20000tr (-123.1eval)
 trainer  7274b4c5-2110-40e7-bb7b-5cd606d49fcf  4.4s:    54tr/s,  104recv/s,   20000tr,   58345recv
 actor0   3f841208-75eb-4f89-a992-0b96796d47e5 13.4s:    90st/s,   90send/s,   57538st,   57532send

[-119.52887930022553, -127.29209348113363, -1.1764847033846308, -120.30537048766564, -117.63027710406459, -122.5436185409344, -114.9043034939532, -129.91358452362329, -124.11005834021489, -117.68628888442618]
```

ちゃんと学習できていますね。
実行が終わったらクラスタとレポジトリ(「Artifact Registry」)は忘れずに削除して課金されないようにしておきましょう。


## 7. その他フレームワークの注意点

細かい動作についてです。

+ Trainer/Actorは再起動すると自動で再学習を始めます。
+ Trainerが再起動すると学習したparameterは残りますが、batchは消えます。
+ Redisが再起動すると学習内容自体が消えます。(ディスクに保存する設定を追加すると残りますが今のところ設定していません)
+ フレームワークの開発状況によって内容等は変化する可能性がある事はご了承ください…


# Atariの学習と課金額の確認

折角なのでAtariを学習させて課金額を見てみました。
Actor数は3でやっています。

・docker image

Atari環境を動作させる必要があるので、動作に必要なライブラリを含めてイメージを作り直します。

``` dockerfile: ./dockerfile
# syntax=docker/dockerfile:1

# --- select image CPU(1.76GB) or GPU(7.38GB)
FROM tensorflow/tensorflow:2.14.0-gpu
#FROM tensorflow/tensorflow:2.14.0

WORKDIR /code
RUN apt-get update \
 && apt install -y --no-install-recommends git \
 && apt-get install -y --no-install-recommends libgl1-mesa-dev libglib2.0-0 \
 && rm -rf /var/lib/apt/lists/* \
 && pip install --no-cache-dir git+https://github.com/pocokhc/simple_distributed_rl@v0.13.3 \
 && pip install --no-cache-dir opencv-python pygame gymnasium redis async_timeout \
# Atari用に追加
 && pip install --no-cache-dir gymnasium[atari,accept-rom-license]

COPY server_*.py /code/

```

・クラスタ（記載がない箇所はデフォルト）

+ ゾーン: us-west1-b
+ ノードプール1(default)
   + ノード数 : 3
   + マシンの構成 : 汎用
   + マシンタイプ : e2-medium
   + ディスク : 標準永続ディスク、30GB
   + SpotVM
+ ノードプール2
   + ノード数 : 1
   + マシンの構成 : GPU、NVIDIA T4、ドライバーはGoogleが管理
   + マシンタイプ : n1-standard-4
   + ディスク : 標準永続ディスク、30GB
   + SpotVM

・マニフェストファイル（関係箇所のみ）

``` yaml: gke/actor.yaml
spec:
  replicas: 3
```

``` yaml: gke/trainer-redis.yaml
spec:
  nodeSelector:
    cloud.google.com/gke-accelerator: "nvidia-tesla-t4"
```

・学習ファイル

``` python
import os

import srl
from srl.algorithms import dqn
from srl.base.define import EnvObservationTypes
from srl.rl.processors.image_processor import ImageProcessor
from srl.runner.distribution import RedisParameters, TaskManager
from srl.utils import common

TRAIN_COUNT = 200_000
ACTOR_NUM = 3
redis_params = RedisParameters(host="xxx.xxx.xxx.xxx", port=16379)
checkpoint_dir = os.path.join(os.path.dirname(__file__), "_atari_checkpoint")
history_dir = os.path.join(os.path.dirname(__file__), "_atari_history")


def _create_runner():
    env_config = srl.EnvConfig(
        "ALE/Pong-v5",
        kwargs=dict(frameskip=7, repeat_action_probability=0, full_action_space=False),
    )
    rl_config = dqn.Config(
        batch_size=32,
        target_model_update_interval=10_000,
        discount=0.99,
        lr=0.00025,
        enable_reward_clip=False,
        enable_double_dqn=True,
        enable_rescale=False,
    )
    rl_config.memory.warmup_size = 1_000
    rl_config.epsilon.set_linear(TRAIN_COUNT, 1.0, 0.1)
    rl_config.memory.capacity = 10_000
    rl_config.memory.set_proportional_memory(beta_steps=TRAIN_COUNT)
    rl_config.image_block.set_r2d3_image()
    rl_config.hidden_block.set_mlp((512,))
    rl_config.window_length = 4

    # カスタムしたprocessorを追加
    rl_config.processors = [
        ImageProcessor(
            image_type=EnvObservationTypes.GRAY_2ch,
            trimming=(30, 0, 210, 160),
            resize=(84, 84),
            enable_norm=True,
        )
    ]
    rl_config.use_rl_processor = False  # アルゴリズムデフォルトのprocessorを無効にする

    runner = srl.Runner(env_config, rl_config)
    return runner


def create_task():
    runner = _create_runner()
    runner.train_distribution_start(
        redis_params,
        actor_num=ACTOR_NUM,
        max_train_count=TRAIN_COUNT,
    )


def wait_task():
    task_manager = TaskManager(redis_params)
    task_manager.train_wait(
        checkpoint_save_dir=checkpoint_dir,
        history_save_dir=history_dir,
    )


def eval_task():
    task_manager = TaskManager(redis_params)
    runner = task_manager.create_runner()
    if runner is None:
        print("Task not found.")
        return
    print(runner.evaluate(max_episodes=1))


def eval_checkpoint():
    runner = _create_runner()
    runner.load_checkpoint(checkpoint_dir)
    runner.animation_save_gif(os.path.join(os.path.dirname(__file__), "_atari.gif"))


def view_history():
    history = srl.Runner.load_history(history_dir)
    history.plot(ylabel_left=["eval_reward0"])


if __name__ == "__main__":
    common.logger_print()
    create_task()
    wait_task()
    eval_task()
    eval_checkpoint()
    view_history()

```

・学習終了時のClientログ

``` bash
17:38:40 ACTIVE 224.3m(22.28s left), 199652tr (-20eval)
 trainer  5990a2a9-f025-41ac-9f43-dc2743969646  7.1s:    15tr/s,   41recv/s,  199808tr,  646725recv
 actor0   774c1673-11c4-4599-abbf-9815d40a3790  6.1s:    13st/s,   13send/s,  204953st,  204952send
 actor1   d7c10d5d-0f5f-40f7-be64-4f6f82ec66aa 13.1s:    14st/s,   14send/s,  241661st,  241660send
 actor2   4be4cf12-9ff4-4126-8796-ca434b882669 11.1s:    12st/s,   12send/s,  199827st,  199825send
17:39:01 END 224.6m( 0.00s left), 200000tr ( 2eval)
 trainer  5990a2a9-f025-41ac-9f43-dc2743969646 29.2s:    16tr/s,   21recv/s,  200000tr,  647181recv
 actor0   774c1673-11c4-4599-abbf-9815d40a3790 20.2s:    14st/s,   14send/s,  205258st,  205257send
 actor1   d7c10d5d-0f5f-40f7-be64-4f6f82ec66aa 26.2s:    19st/s,   19send/s,  242070st,  242068send
 actor2   4be4cf12-9ff4-4126-8796-ca434b882669 25.2s:    13st/s,   13send/s,  200118st,  200117send
```

・学習過程

時間がかかりすぎるので学習回数は少なめにしています。（今回20万回の学習に対して、DQN論文は5000万フレームの学習）
Pongは最低スコアが-20で最大スコアが20です。まだまだスコアは伸びそうですね。

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/396150/cffa1202-d799-3c1f-c531-ae5ab6050781.png)


・学習後の結果

（長くてサイズが重いので300frameで切り上げています）

![_atari.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/396150/ab4c6370-19d0-95b8-5219-9e5bcf017de8.gif)



## 課金内容

課金ですが、1日794円ほどでした。(1ドル150円ほど)
1日単位でしか表示されず結構試行錯誤していたのであくまで目安です。

+ 実施項目
   + クラスタの作成・削除を数回実施
   + Pendulum-v1 の学習（10分ぐらい）
   + Atariの学習（4時間ほど）

+ 費用内訳
   + Artifact Register: 2円
   + Compute Engine: 635円
   + Kubernetes Engine: 107円
   + Networking: 50円


# おわりに

いろいろありますが、まず思ったより課金されないイメージでした。
AtariではGPUが40%ぐらい常に使われていましたが、1000円いかないのは意外でした。
どちらかというとクラスタの作成/削除で結構リソースが使われているような…？

次にノードのアサインが大変でした…。
もっと簡単にGPUの高性能サーバが使えると思ったのですが、結構アサイン失敗しました。
知識不足でやり方が悪い可能性もあるのでもっと勉強しないと…

ただこれで大量のリソースを使える基盤を整えれたのは大きいかと思います。