Google Cloud Platform

GCP による深層強化学習 : ハイパーパラメータ調整と Cloud ML Engineで OpenAI Gym ゲームを攻略

機械学習に関する研究の多くの分野と同様に、強化学習(RL : Reinforcement Learning)が猛烈なスピードで進歩しています。他の研究分野もそうですが、研究者たちはディープ ラーニングを活用して最先端の成果を生み出しています。特に強化学習は、ゲーム分野で従来の機械学習テクニックの性能を大きく上回り、Atari ゲームでは人間のレベルに追いつくどころか世界最高水準にまで達しました。囲碁の世界でも人間のチャンピオンに勝ち、StarCraft II のような難易度の高いゲームでも将来が楽しみな結果を出しています。強化学習はさらに、自動運転車推薦システム入札機械学習モデル開発といった高度な分野で効果を発揮しています。

深層強化学習でのエージェントのトレーニングにはコンピューティング リソースと時間の両面で高いコストがかかります。通常は、アルゴリズムが数万もしくは数百万ステップのシミュレーション / トレーニングを経て初めて、累積報酬が上昇を開始します。たとえば、DeepMind の論文によると、エージェントが学習成果をはっきり示すまでにおおよそ 20 エポック(同論文によれば、1 エポック = 0.5 時間)を要しています。プロセス全体では 100 エポックかかり、しかもそれはわずか 1 回のトライアルでした。

また、ハイパーパラメータに敏感なことも強化学習の短所として知られています。これは、多くの異なるハイパーパラメータを評価する必要があるということを意味します。

この投稿記事では、こうした点への対応を目標としたうえで、Cloud Machine Learning(ML)Engine のハイパーパラメータ調整サービスを使って多くのジョブを並列にトレーニングする方法を説明します。このサービスには次の 2 つの大きなメリットがあります。

多くのモデルを並列にトレーニングすることができます。そのため、コンセプトを高速に反復でき、利用料金については各ジョブで使ったコンピューティング リソースの分だけに抑えられます。
単純なグリッド検索よりも一般に収束が速いマネージド ハイパーパラメータ調整サービスのメリットを享受できます。

これらの特長はあらゆる機械学習にとって有益ですが、計算が重く、異なるハイパーパラメータを評価するために多くのトライアルがしばしば必要とされる強化学習では特に大きな効果を発揮します。

本稿で使用するコードは GitHub リポジトリのここここに置いてあります。このコードは強化学習を Google Cloud Platform(GCP)で使用する際の実例となっており、より洗練させればさらに良い結果を生み出せるはずです。

強化学習入門

強化学習は機械学習の形態の 1 つであり、エージェントが環境に対する行動を選択しながら、その一連の選択を通じて得られる目標(報酬)を最大化する方法を学習していくというものです。従来の教師あり学習のテクニックとは異なり、すべてのデータ ポイントにラベルが付けられているわけではなく、エージェントは「疎」な報酬にアクセスできるだけです。

強化学習の歴史は 1950 年代までさかのぼることができ、そのアルゴリズムは数多く存在しますが、最近では簡単に実装できる 2 つの強力な深層強化学習アルゴリズム、DQN(Deep Q-Network)と DDPG(Deep Deterministic Policy Gradient)が注目を集めています。この節では、両アルゴリズムとその変種について簡単に紹介します。
1_QMAP47

強化学習のプロセス概念図

DQN とは何か

DQN は、Google DeepMind のグループが 2015 年に Nature の論文で発表したアルゴリズムです。論文の著者らは、画像認識分野でのディープ ラーニングの成功に励まされ、ディープ ニューラル ネットワークを Q 学習に組み込み、観測空間が非常に高次元な Atari Game Engine Simulator でアルゴリズムをテストしました。

ディープ ニューラル ネットワークは、特定の入力状態に基づいて、出力 Q 値、すなわちある行動を取ることがどの程度望ましいかを予測する関数近似器として機能します。つまり、DQN は価値ベースのアルゴリズムです。DQN はトレーニング アルゴリズムの中でベルマン方程式に従い Q 値を更新していきますが、動くターゲットに合わせる難しさを避けるために、ターゲットの値を予測する第 2 のディープ ニューラル ネットワークを使います。このネットワークの重みは指定されたインターバルでしか更新されず、それによって DQN のトレーニングを助けます。

強化学習は絶えず環境とやり取りすることによってデータを収集するので、データには時間相関があり、それがトレーニング中に DQN が収束するのを妨害します。そこで著者らは、この問題に対処するためにリプレイ バッファを導入しました。リプレイ バッファは以前に収集したデータを保持し、各トレーニングの反復中はこのリプレイ バッファからミニバッチをサンプリングします。

DeepMind グループが DQN の構築で使用したテクニックの詳細については、こちらの論文をご一読ください。

DDPG、TD3、C2A2

DDPG も DQN と同様に価値ベースの強化学習アルゴリズムです。具体的には、Actor(行動器)が環境の状態に基づいて行動を選択し、Critic(評価器)が与えられた状態(または状態と行動のペア)の価値を推計する Actor-Critic のアルゴリズムです。DQN と同じように、DDPG も Actor と Critic の両関数の近似器としてディープ ニューラル ネットワークを使用します。

DDPG には、Critic が学習プロセスで過大評価をしがちだという大きな問題があります(これは、DQN を含む価値ベースの強化学習の大半に見られる問題です)。この問題に対処するため、TD3(Twin Delayed Deep Deterministic policy gradient)は 2 つの Critic を持ち、(状態, 行動)のペアに対する両者の評価のうち小さいものを推計値として返すことを提案しています(下記コードの 6-11 行)。TD3 はこれ以外にも、推計誤差の拡大を抑えるためにターゲット ネットワークの更新を遅らせることや、値評価で狭いピークへの過剰適合を防ぐためにターゲット ポリシーを平滑化する正則化も提案しています。TD3 の詳細についてはこちらの論文をご覧ください。

TD3 は優れた機能を発揮し、実装も簡単ですが、エージェントが推計値を低いと思ったときに取れる行動にも選択肢があったほうがよい(そして面白い)でしょう。そこで私たちは、TD3 に Actor をもう 1 つ追加した C2A2 というアルゴリズムを作りました(名前の由来は言うまでもないはずです)。C2A2 は値評価では TD3 と同じですが、追加された Actor が行動の選択肢を提供し、エージェントは推計値が大きい Actor を選択します。下記コードの 18-27 行はこのロジックに相当し、Cloud ML Engineで実行する新しいアルゴリズムを作るうえでプラットフォームに合わせたコード変更は不要だということを示しています。
  # DDPG's way of value estimation
   def get_qval(self, observation, action):
       """Get Q-val."""
       return self.critic.get_qval(observation, action)

   # TD3 and C2A2's way of value estimation    
   def get_qval(self, observation, action):
       """Get Q-val."""
       q_val1 = self.critic1.get_qval(observation, action)
       q_val2 = self.critic2.get_qval(observation, action)
       return np.minimum(q_val1, q_val2)

   # DDPG and TD3's way for action selection
   def action(self, observation):
       """Return an action according to the agent's policy."""
       return self.actor.get_action(observation)

   # C2A2's way for action selection
   def action(self, observation):
       """Return an action according to the agent's policy."""
       action1 = self.actor1.get_action(observation)
       action2 = self.actor2.get_action(observation)
       q_val1 = self.get_qval(observation, action1)
       q_val2 = self.get_qval(observation, action2)
       q_val1 = np.expand_dims(q_val1, axis=-1)
       q_val2 = np.expand_dims(q_val2, axis=-1)
       return np.where(q_val1 > q_val2, action1, action2)

OpenAI Gym 環境での深層強化学習

OpenAI Gym は、強化学習アルゴリズムの試験や評価に使用できる人気の高いプラットフォームです。OpenAI Gym を使えば、エージェントがやり取りする環境を初期化できます。
  import gym
env = gym.make('BipedalWalker-v2')
obs = env.reset()

エージェントがすでにトレーニング済みの場合、エージェントは環境の観測結果を入力として行動を選択します。

  agent = Agent(...)
done = False
while not done:
    action = agent.take_action(obs)
    obs, reward, done , info = env.step(action)

Cloud ML Engine で強化学習を実行するためのステップ

Google Cloud でハイパーパラメータ調整のジョブを実行するのは簡単です。必要なステップは次の 5 つです。

  1. 通常の方法でモデルを構築します。
  2. コマンドライン引数としてハイパーパラメータを指定します。これにより、モデルのロジックからハイパーパラメータが切り離されます。
  3. Python パッケージを作成します(つまり、__init__.py と setup.py を追加します)。
  4. hyperparameter.yaml ファイルを記述します。
  5. コマンドラインで gcloud ml engine submit と入力し、最初のトレーニング ジョブを送信します。

実際の作業では大半の時間がステップ 1 に費やされ、他の部分は定型のコードで事足ります。私たちの構成では、OpenAI の gym ライブラリと互換性のあるシンプルな方策勾配エージェントと DQN エージェントを実装し、さらに continuous control problems のために TD3 を実装しました。実装はここここからチェックアウトできます。

2_8VlF42R.max-1100x1100
Cloud ML Engine へのハイパーパラメータ調整ジョブの送信。コマンドラインの --config <YAML ファイル> を使ってハイパーパラメータを渡す必要があります

ハイパーパラメータ調整 : コードの準備

強化学習エージェントを作り、解決したいタスクを定義したら、コマンドライン引数にハイパーパラメータを指定する必要があります。これは、すべてのハイパーパラメータを互いに切り離し、個々のハイパーパラメータの効果をそれぞれ評価するうえで、きわめて重要です。たとえば argparse ライブラリを使用すると、ハイパーパラメータをコマンドライン引数として指定できます。
  import argparse
parser = argparse.ArgumentParser()
...
parser.add_argument(
    '--batch_size',
    help = 'Batch size for agent.',
    type = int,
    default = 64
)
...
args = parser.parse_args()
run_model(args)
ハイパーパラメータ調整のために 2 つの重要な追加をモデルに対して行います。1 つは最適化したい指標の指定、そしてもう 1 つは個々のモデル出力への一意な名前の指定です。

Keras では、最適化したい指標を Model オブジェクトの metrics 引数に指定できます。

  model = Model(...)
...
model.compile(loss='rmse', metrics=['rmse'])

カスタム指標を追加したい場合もあるでしょう。私たちは次のようにして reward という名前のカスタム指標を追加しました。

  # Initialize a tensor.
import keras.backend as K
reward_tensor = K.variable(value=0)

# Create custom reward.
K.get_session().run(tf.global_variables_initializer())
def reward(y_true, y_pred):
    return self.reward_tensor

model.compile(loss='rmse', metrics=reward])

# At each iteration, update tensor:
for _ in range(nepochs):
    ...
    K.set_value(self.reward_tensor, self.cur_reward)
    ...

TensorFlow を使用している場合は、TensorBoard と同様に指標のログを取得する必要があります。

  episode_reward = tf.placeholder(dtype=tf.float32, shape=[])
tf.summary.scalar('reward', episode_reward)

with tf.Session() as sess:
    ...
    merge = tf.summary.merge_all()
    summary = sess.run(merge, feed_dict={episode_reward: reward})
    train_writer.add_summary(summary, iteration)
    ...
詳細は後述しますが、この reward パラメータを YAML ファイルで最適化するよう Cloud ML Engine に指示します。

次に、2 番目の重要な追加として、個々のトライアルごとに一意な出力ディレクトリを用意します。トライアルが互いに上書きし合うようなことは避けなければなりません。これは、コマンドラインで --job-dir フラグを使用すれば自動的に実現できますが、TF_CONFIG 環境変数からトライアル番号を手作業で取得することも可能です。

  # Append trial_id to path if we are doing hp tuning.
trial_id = json.loads(
                  os.environ.get('TF_CONFIG', '{}')
                   ).get('task', {}).get('trial', '')
model_dir_hp = os.path.join(model_dir, trial_id)

さらに、コードを Python パッケージの形式にまとめる必要もあります。詳細はこちらにありますが、基本的には空の __init__.py  setup.py をフォルダ構造に追加するということです。私たちの最新コードのファイル構成は以下のとおりです。

  rl_on_gcp # root directory
   setup.py # says how to install your package
   trainer # package name, “trainer” is convention
      # .py files specific to your job, e.g. model.py
      __init__.py # python convention indicating this is a package

ハイパーパラメータ調整 : ジョブの送信

モデルを作成したら、調整したいハイパーパラメータ、個々のハイパーパラメータの範囲、最適化したい指標(ここでは reward)を指定するハイパーパラメータ yaml ファイルを定義します。

ここでは、どれだけの計算能力を使いたいかを示すスケール階層も指定できます。私たちの場合は 1 つのジョブで 1 つの GPU を使いますが、たとえば複数の GPU を使うようなカスタム セットアップの場合はカスタム スケール階層を指定できます。機械学習に Cloud ML Engine を使うことのメリットは、インフラのことを気にせずにモデル開発とデプロイに専念できることです。

注意すべきポイントは、ベイズ最適化を使うハイパーパラメータ調整サービスは前のステップから学習する逐次アルゴリズムだということです。したがって、 maxTrials maxParallelTrials を指定しなければなりません。

maxParallelTrials  maxTrials と等しいという極端なケースでは、プロセスは 1 つのモデルのトレーニングに要する時間と同じくらいの短時間で終わります(この場合、algorithm パラメータにはベイズ最適化ではなく、グリッド検索を行う GRID_SEARCH を指定できます)。逆に、 maxParallelTrials が小さければトレーニングに要する時間は長くなりますが、ベイズ最適化プロセスはエポックが増え、それによってモデルを少しずつ良くすることができるので、一般に性能の高いモデルが得られます。以下に例を示しましょう。
  trainingInput:
  scaleTier: BASIC_GPU
  hyperparameters:
    maxTrials: 40
    maxParallelTrials: 5
    enableTrialEarlyStopping: False
    goal: MAXIMIZE    
    hyperparameterMetricTag: reward
    params:
    - parameterName: update_target
      type: INTEGER
      minValue: 5000
      maxValue: 20000
      scaleType: UNIT_LOG_SCALE
    - parameterName: init_eta
      type: DOUBLE
      minValue: 0.8
      maxValue: 0.95
      scaleType: UNIT_LOG_SCALE
    - parameterName: learning_rate
      type: DOUBLE
      minValue: 0.00001
      maxValue: 0.001
      scaleType: UNIT_LOG_SCALE
    - parameterName: batch_size
      type: DISCRETE
      discreteValues:
      - 4
      - 16
      - 32
      - 64
      - 128
      - 256
      - 512
YAML ファイルには指標の名前(ここでは reward)と目標(この指標の最大化)を指定します。同様に、私たちはハイパーパラメータのチューナーにもバジェットを与えています。私たちは全部で 40 のモデルをトレーニングすることが認められており、そのうちの 5 個がいつでも並列実行されます。最後に、モデル内の調整可能なハイパーパラメータ(learning_rate、batch_size など)と、個々のハイパーパラメータの範囲を指定します。

以上で、Cloud ML Engine にハイパーパラメータ ジョブを送信する準備が整いました。

  JOBNAME=rl_job_hp_$(date -u +%y%m%d_%H%M%S)
REGION=us-central1
BUCKET=your-bucket

gcloud ml-engine jobs submit training $JOBNAME \
        --package-path=$PWD/rl_on_gcp/trainer \
        --module-name=trainer.trainer \
        --region=$REGION \
        --staging-bucket=gs://$BUCKET \
        --config=hyperparam.yaml \
        --runtime-version=1.10 \
        --\
        --steps=100000\
        --start_train=10000\
        --buffer_size=10000\
        --model_dir='gs://your-bucket/rl_on_gcp'
ここでは、 --config フラグを使ってハイパーパラメータの YAML ファイルを指定している点に注意してください。基本的にこのプロセスは、異なるハイパーパラメータを使用して、さまざまなトレーニング ジョブを実行します。

ジョブ ステータスは次の方法で表示できます。


モデルの出来は?

GCP 上で強化学習を実行するための参照例として、私たちは以下の 3 つを作成しました。

  1. TensorFlow と OpenAI Gym を使用して CartPole(倒立振子)を解く、非常にシンプルな方策勾配の実装
  2. tf.keras を使用して Breakout Gym 環境を解く、より高度な DQN 実装
  3. BipedalWalker 環境で TensorFlow を使用する TD3

これら 3 つの実装すべてについて、得られた結果(ハイパーパラメータの調整結果を含む)とともに、CartPole、Breakout、BipedalWalker を解いているエージェントのビジュアルをお見せしましょう。すべてのジョブを Cloud Storage 上のモデル ディレクトリに書き込んだので、TensorBoard を使ってトライアル結果を可視化することができます。
3_KJTN4C%

さまざまなトライアルに対する報酬の経時的な変化(TensorBoard で表示)

4_eleuRy9

TD3 アルゴリズムのハイパーパラメータ調整から得られた学習曲線(学習プロセスを通じた各エピソードでの累積報酬)

pasted_image

上記トライアルの中で最良のものから得られた学習曲線。

BipedalWalker は、100 エピソード連続で平均報酬が持続的に 300 に達することができれば、「解けた」と見なすことができます。私たちがトレーニングしたエージェントは、エピソード 2000 までにこの基準を満たし、私たちを Leaderboard の 3 位(2018 年 12 月 10 日の本稿執筆時点)に押し上げました。ここにあるハイパーパラメータ調整の設定を書き換えて、もっと高いスコアが得られるかを試してみてください。

複数のトライアルの評価曲線を解釈するのは少し難しいかもしれません。そこで私たちは、最終的な目標値だけを見るようにしています。最終結果は Cloud Console の ML Engine > Jobs から確認できます。

training output
Cloud Console に表示された出力の例
強化学習をテーマにした投稿記事で、エージェントがゲームをプレイしているところを示す GIF アニメーションがないと物足りなさを感じますので、いくつか紹介しましょう。

CartPole をプレイする方策勾配の実装では、最良のパラメータ(勾配ステップは 30 に制限。十分に反復すれば、最適とは言えないハイパーパラメータの場合でも、CartPole は最終状態の報酬 200 にたどり着きます)は次のとおりです。

  "discount_rate": "0.83375867407642434",
        "n_games_per_update": "4",
        "n_hidden": "16",
        "learning_rate": "0.055623309804870541"
7

最良のパラメータを使って勾配をわずか 30 回更新しただけの CartPole

これに対し、見劣りのするハイパーパラメータを使用すると、環境が最終状態に達することはなく、報酬はわずか 32 にとどまりました。

8

私たちはもっと複雑な Gym 環境を解くための DQN 実装も作成し、そのコードを Breakout で実行しました。ここでは、比較的良好なハイパーパラメータを使用した結果を 2 つ紹介します。トレーニング時間を延長し、大きなハイパーパラメータ スペースを検索すれば、性能は間違いなく向上します。

9
約 30 万イテレーションで、最大報酬が 18 に達した Breakout
10
約 100 万イテレーションで、最大報酬が 36 に達した Breakout

BipedalWalker の結果

わずか 100 エピソードのトレーニングでは、エージェントはほとんど立つことができませんでした。
ja_game_gif1

1000 エピソードのトレーニング後は、エージェントはコースを渡り切ることができました(非効率的な形ですが)。

ja_game_gif2

2000 エピソードのトレーニング後は、エージェントの足取りがすばやくなりました。

ja_game_gif3

まとめ

強化学習は現在、急速なイノベーションの時代を迎えています。Cloud ML Engine も、強化学習による問題解決の実践者や研究者にとって価値の高いツールとなっています。特に、Cloud ML Engine のハイパーパラメータ調整サービスは、異なるタイプのハイパーパラメータの組み合わせを評価することを可能にします。また、単純なグリッド検索と比べて最適化プロセスを高速化できるベイズ最適化を使ったマネージド ハイパーパラメータ調整サービスのメリットも享受できます。ハイパーパラメータ調整の詳細については、こちらのドキュメント ページをご覧ください。

参考資料

- By Praneet Dutta, Cloud ML Engineer and Chris Rawles, ML Solutions Engineer and Yujin Tang, ML Strategic Cloud Engineer