Translate

2020年9月14日月曜日

Stable Baselinesのコールバックの書き方を調べてみる

Donkeycar の OpenAI Gym 環境 gym-donkeycar を理解するためには OpenAI Gymのフレームワークを手っ取り早く把握する必要がある。

 ということで書店でみつくろい、比較的カラフルで分厚い「OpenAI Gym/Baselines 深層学習 強化学習 人工知能プログラミング 実践入門」という書籍を買った。

 

過去に一度さらっとTensorflo(v1の頃だけど)強化学習のチュートリアルをやって、だいぶ時間がたっていたが、必要な箇所しか説明していなかったので、すんなりすすめることができた。

 本に書いているコードに多少誤り(breakぬけとか)があり、治す必要はあるが Python を知っている人だったら問題ない程度..

 

..だったのだけど、Stable Baselinesのコールバックを使うところで、サンプルコードが動かな書くなった..

..動かなくなった時点でピンときたのが、後付で読んだアマゾンのレコメンド、情報が古い云々..ああ、Stable Baselinesのコールバック実装が変わったんじゃないの、とピンときた。

 

で公式ドキュメントの Callbacks を読んでみたら、案の定"レガシ"になってた..

 

以下、勝手翻訳した内容です。

(ただしレガシとかかれた旧呼び出しに関する箇所はomitしました)

参照はat your own riskでお願いします。

 ------

コールバック


コールバックは、トレーニング手順の特定の段階で呼び出される関数のセットです。トレーニング中にコールバックを使用して強化学習(RL)モデルの内部状態にアクセスできます。監視、自動保存、モデル操作、進行状況バーなどの機能を提供することができます。

独自 Callback


独自の Callback クラスを構築するには、BaseCallback を継承したクラスを作成する必要があります。これにより、イベント( _on_training_start _on_step )や便利な変数(RLモデルの self.model など)にアクセスできます。

ここではカスタムコールバックの2つのサンプルを紹介します。1つはトレーニングの報酬に応じて最適なモデルを保存するためのもの(セクション「サンプル」を参照)と、Tensorboardを使用して追加の値をログに記録するためのもの(「Tensorboard」セクションを参照)です。

from stable_baselines.common.callbacks import BaseCallback


class CustomCallback(BaseCallback):
    """
    BaseCallback クラスを継承したカスタムコールバック

    :param verbose: (int) Verboseレベル 0:出力なし 1: infoのみ 2: debugレベルまで
    """
    def __init__(self, verbose=0):
        super(CustomCallback, self).__init__(verbose)
        # 以下の変数にはコールバック内でアクセス可能
        # (ベースクラス上で定義済み)
        # 強化学習モデル
        # self.model = None         # タイプ: BaseRLModel
        # トレーニングに使用される環境のエイリアス
        # self.training_env = None  # タイプ: Union[gym.Env, VecEnv, None]
        # コールバックが呼び出された回数
        # self.n_calls = 0          # タイプ: int
        # self.num_timesteps = 0    # タイプ: int
        # ローカル変数辞書、グローバル変数辞書
        # self.locals = None        # タイプ: 辞書[文字列, Any]
        # self.globals = None       # タイプ: 辞書[文字列, Any]
        # レポートに使用されるロガーオブジェクト
        # self.logger = None        # タイプ: logger.Logger
        # # イベントコールバックでは、親オブジェクトにアクセスできると
        # # 便利な場合がある
        # self.parent = None        # タイプ: オプション[BaseCallback]

    def _on_training_start(self) -> None:
        """
        このメソッドは最初のロールアウトが始まる前に呼び出される。
        """
        pass

    def _on_rollout_start(self) -> None:
        """
        ロールアウトは、現在のポリシを使用した環境対話の集合である。
        このイベントは、新しいサンプルを収集する前にトリガされる。
        """
        pass

    def _on_step(self) -> bool:
        """
        このメソッドは env.step() が呼び出されるたびに
        モデルによって呼び出される。

        (EventCallback の) 子コールバックの場合、イベントがトリガされた
        ときに呼び出される。

         :return:(真偽値) コールバックが False を返した場合、トレーニングは早期中止される.
        """
        return True

    def _on_rollout_end(self) -> None:
        """
        このイベントは、ポリシを更新する前にトリガされる。
        """
        pass

    def _on_training_end(self) -> None:
        """
        このイベントは learn() メソッド終了前にトリガされる。
        """
        pass


注意
self.num_timesteps は、環境で実行されたステップの総数に対応する。つまり環境の数に env.step() が呼び出された時間の数を掛けたものである。
PPO1および TRPO は、 MPI に依存しているため、(各ステップではなく)各ロールアウト後に self.num_timesteps を知って更新する。
その他のアルゴリズムの場合、self.num_timestepsn_envsenv.step() を呼び出すたびに、(環境数)ずつ増加する。


注意
SAC、DDPG、TD3、DQNなどのオフポリシアルゴリズムの場合、rollout の概念は、2つの更新の間の環境で実行されるステップに対応する。



EventCallback


Kerasと比較すると、Stable Baselinesは EventCallback というイベントをトリガするための BaseCallback のセカンドタイプを提供します。イベントがトリガされると、子コールバックが呼び出されます。

例として、EventCallbackのサブクラスのEvalCallback (後述セクション参照)は、新しい最適なモデルがある場合、その子のコールバックをトリガします。子コールバック StopTrainingOnRewardThreshold の場合、強化学習モデルによって達成された平均報酬がしきい値を超えた場合にトレーニングを停止します。

注意
EvalCallbackStopTrainingOnRewardThreshold のソースコードを確認して、この種のコールバックで実現できることの概要を理解することを推奨。


class EventCallback(BaseCallback):
    """
    イベント発生時にコールバックをトリガするためのベースクラス。

    :param callback: (オプション[BaseCallback]) イベントがトリガーされた時に呼び出されるコールバック
    :param verbose: (int)
    """
    def __init__(self, callback: Optional[BaseCallback] = None, verbose: int = 0):
        super(EventCallback, self).__init__(verbose=verbose)
        self.callback = callback
        # 親にアクセスする権限を与える
        if callback is not None:
            self.callback.parent = self
    ...

    def _on_event(self) -> bool:
        if self.callback is not None:
            return self.callback()
        return True


Callbackコレクション


安定したベースラインは、以下のような共通コールバックセットを提供します。
 

  • モデルを定期的に保存する (CheckpointCallback)
  • モデルを定期的に評価し最適なモデルを保存する (EvalCallback)
  • コールバックの連鎖 (CallbackList)
  • イベントのコールバックのトリガ(イベントコールバック、EveryNTimesteps)
  • 報酬のしきい値に基づいてトレーニングを早期に停止する (StopTrainingOnRewardThreshold)


CheckpointCallback


save_freq ステップごとにモデルを保存する Callback クラスです。ログフォルダ( save_path ) を引数に指定します。またオプションとしてチェックポイントファイルの接頭文字列を指定できます( デフォルトは rl_model )。

from stable_baselines import SAC
from stable_baselines.common.callbacks import CheckpointCallback
# 1000ステップごとにチェックポイントを保存する
checkpoint_callback = CheckpointCallback(save_freq=1000, save_path='./logs/',
                                         name_prefix='rl_model')

model = SAC('MlpPolicy', 'Pendulum-v0')
model.learn(2000, callback=checkpoint_callback)



StopTrainingOnRewardThreshold


一時的な報酬(評価に対する平均エピソード報酬)のしきい値に達したら(つまりモデルが十分に良いときに)、トレーニングを停止します。EvalCallback と一緒に使用し、新しい最良のモデルによってトリガされるイベントを使用する必要があります。

import gym

from stable_baselines import SAC
from stable_baselines.common.callbacks import EvalCallback, StopTrainingOnRewardThreshold

# 評価用環境
eval_env = gym.make('Pendulum-v0')
# モデルが報酬のしきい値に達したときにトレーニングを停止する
callback_on_best = StopTrainingOnRewardThreshold(reward_threshold=-200, verbose=1)
eval_callback = EvalCallback(eval_env, callback_on_new_best=callback_on_best, verbose=1)

model = SAC('MlpPolicy', 'Pendulum-v0', verbose=1)
# タイムステップの数はほぼ無限(1e10)だが、
# 報酬のしきい値に達するとすぐにトレーニングを停止する
model.learn(int(1e10), callback=eval_callback)



EveryNTimesteps


n_steps ステップごとに子コールバックをトリガするイベントコールバック。

注意
PPO1やTRPOはMPIに依存するため、s_stepは2つのイベント感の下限となる。


import gym

from stable_baselines import PPO2
from stable_baselines.common.callbacks import CheckpointCallback, EveryNTimesteps

# これは CheckpointCallback (save_freq = 500) を定義することと同等
# checkpoint_callback は 500 ステップごとにトリガされる
checkpoint_on_event = CheckpointCallback(save_freq=1, save_path='./logs/')
event_callback = EveryNTimesteps(n_steps=500, callback=checkpoint_on_event)

model = PPO2('MlpPolicy', 'Pendulum-v0', verbose=1)

model.learn(int(2e4), callback=event_callback)



レガシ:機能的アプローチ


警告
以下書かれているコールバックの実装方法は、オブジェクト指向アプローチを支持するため
廃止となった。
(以下略)


----


本読んだときのコールバック、たしかに実装がダサかった。

OpenAI Gymの環境クラスって、ビジネスアプリケーションの設計アプローチで書かれている。理由はシミュレータが業務アプリと科学技術計算のファサードだから。

そこが理解できるかできないかで、強化学習をさくっと一般業務アプリケーションへ適用できるかどうかのスピードが変わる。

 とはいえトレーニング実装は、いわゆる機械学習ライブラリ側の責務なので、無理にオブジェクト指向にシなくてもいいと思っている。..けど、実装しやすいPyTorchにTensorFlowが押されているのって、科学技術計算指向脳で設計されいたライブラリだからなので、今の風潮はこっちが正解なのかもしれない..

 

..まあ、使わせて頂く見としては、どうでもいいけど..

既存アプリケーションをK8s上でコンテナ化して動かす場合の設計注意事項メモ

既存アプリをK8sなどのコンテナにして動かすには、どこを注意すればいいか..ちょっと調べたときの注意事項をメモにした。   1. The Twelve Factors (日本語訳からの転記) コードベース   バージョン管理されている1つのコードベースと複数のデプロイ 依存関係 ...