失敗からも学ぶ 強化学習 HERのアルゴリズムを理解して、OpenAI Gymの新しいロボットで試してみた

# 概要
OpenAI Gymが新しい環境Roboticsを公開したので（[公式ブログ](https://blog.openai.com/ingredients-for-robotics-research/)）試してみました。 また、その中でHER（Hindsight Experience Replay）で、良いパフォーマンスが出たそうなので、アルゴリズムを理解し、動かしてみました。

<img width="650" alt="Picture1.gif" src="https://qiita-image-store.s3.amazonaws.com/0/105335/a0bf0590-92bd-8830-ab72-1fb93a9e75d3.gif">


# 環境作り
Python3.5.2の環境で、OpenAI GymのインストールとMuJoCoとmujoco-pyのインストールが必要です。
MoJoCoを使うのはライセンスが必要ですが、一旦、無償版(30日間)を使いました。


[MuJoCoのライセンス取得](https://www.roboti.us/license.html)
[MuJoCoとMuJoCo-pyのインストール方法（公式GitHub）](https://github.com/openai/MuJoCo-py#install-MuJoCo)
[OpenAI　Gymのインストール方法（公式GitHub）](https://github.com/openai/gym#id7)

# 環境の種類
今回、８つのRobotics環境が公開されました。
Fecthと呼ばれる、物を摘めるようなロボットと、ShadowHandという手の動きを再現しているロボットがあります。

それぞれの環境の目的は以下の通りです。

<img width="812" alt="Screenshot 2018-03-01 14.13.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/105335/3768f7e8-7f93-921c-1e54-d959261227c2.png">


# HER（Hindsight Experience Replay）
## 考え方
私たち人間はボールを転がした時、もしも目的の場所に辿り着かなくても、
「今の感じで転がすとあの辺に行くのか。ということは...」
と、失敗してもその体験を次のチャレンジに活かします。

強化学習でも同じように、試行結果が失敗で報酬が無くても、その試行データを利用して目的の達成に役立てよう、という考えです。

<img width="800" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/105335/c1c4e37b-819c-97cd-c012-e880d0300ca1.png">

## 特徴
通常の価値関数は「ある状態$s$である行動$a$をとる価値」を表しますが、HERでは「ゴール:$g$」というインプットを増やし、「あるゴール$g$に対し、ある状態$s$である行動$a$をとる価値」を価値関数として定めます。

一度試行してみて、本来のゴールとは違う場所に辿り着いたとしても、後からそこを擬似ゴールとみなして報酬$+1$を設定し、今しがた試行した軌跡は擬似ゴールに辿りつくための成功の軌跡とみなし、その軌跡情報から、価値関数を更新します。

このような（Roboticsのような）問題・環境では、各ゴール間の空間的な距離（各位置）は意味を持っているため、ゴールの位置がインプットになっている価値関数において、失敗による擬似ゴールでの価値関数の更新が有効利用されます。

## アルゴリズム全体
### 準備
* オフポリシーのアルゴリズム($Algorithm$)を選びます。（DQN等）
* 擬似ゴールを設定する戦略($Storategy$)を定めます。
* 報酬関数は $r:S \times A \times G \rightarrow R$ ($G$はゴール位置)

### 学習
１. バッファー$Re$を初期化
２. 以下を必要な回数分繰り返す
　　２-１. 本当のゴール$g$と初期状態$s_0$を定める
　　２-２. 以下を終末まで繰り返す
　　　　２-２-１. $Algorithm$のポリシーからゴール$g$に対する状態$s_t$の時の$a_t$を決めて行動する
　　２-３. 以下を２−２.の軌跡で繰り返す
　　　　２-３−１. ($s_t, s_{t+1}, r_t, a_t, g$)を全て観測して$Re$に記録しておく
　　　　２-３−２. 今回のエピソード２−２.の軌跡から$Storategy$に沿って、擬似ゴール$G$を決める
　　　　２-３−３. 全ての擬似ゴール$g' \in G$において以下を繰り返す
　　　　　２-３−３−１. 擬似ゴールに対する報酬を$r'$として取得する
　　　　　２-３−３−２. ($s_t, s_{t+1}, r'_t, a_t, g'$)を全て観測して$Re$に記録しておく
　　２-４. $Re$からミニバッチを取り出し、$Algorithm$のパラメータ更新を1エポック実施する。


$Storategy$の一番簡単なものは、エピソードの最後の位置を擬似ゴールにすることですが、エピソード内のその時点$t$より先の位置をランダムに複数選択すると、良い場合もあるようです。

<img width="550" alt="Screenshot 2018-03-01 22.33.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/105335/b092cea6-cd9c-c458-f46b-7a3b77a8b85b.png">

* future : エピソード内の、現時点より先の位置を擬似ゴールとする
* episode : エピソード内のランダム時点の位置を擬似ゴールとする
* random : 別エピソードも含めて過去のランダムな時点の位置を擬似ゴールとする
* final : エピソードの最後の時点の位置を擬似ゴールとする

# Baselinesで試す
HERを試すため、OpenAIから出ているBaselinesを利用しました。（⬅︎ 手抜き）
[Baselinesのインストール　公式](https://github.com/openai/baselines)
インストールでmpiのエラーが出たので、以下で対処。

```bash
brew install mpich
pip install mpi4py
```

## 学習
引数で環境の設定を好きなものに書き変えられます。
デフォルトはFetchReach-v0です。今回は変えませんでした。

```bash
python -m baselines.her.experiment.train
```

## 結果
分かりにくいですが、全部成功してます。

```bash
python -m baselines.her.experiment.play {生成されたpklファイルパス}
```

<img width="450" alt="test.gif" src="https://qiita-image-store.s3.amazonaws.com/0/105335/7311cbd2-63d9-34bd-6b45-5949ce79f091.gif">

<img width="450" alt="bokeh_plot.gif" src="https://qiita-image-store.s3.amazonaws.com/0/105335/8c5696ae-9062-5436-d790-eb3a420d16c7.png">


論文ではDDPGだけのものと比較していますが、HERありの方がパフォーマンスが良いのが分かります。

<img width="616" alt="Screenshot 2018-03-01 20.30.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/105335/04b226b6-e7ba-723a-0b6f-644482a92e9e.png">


## 実装して試す
一旦、流れだけ書きました。
ちゃんと書いたら、記事を更新します。

```py
import gym
env = gym.make('FetchSlide-v0')
obs = env.reset()
done = False
R =[]
action_list = []
substitute_goal = None


while not done:
    # off-policyアルゴリズムの方策によって、Actionを決める
    action = policy(obs['observation'], obs['desired_goal'])
    next_obs, reward, done, info = env.step(action)
    # バッファに記録
    R.append((action, obs['observation'], next_obs['observation'],
              reward, obs['desired_goal']))
    # 擬似ゴール用の行動履歴
    action_list.append(action)
    if done:
        # 今辿り着いたところを擬似ゴールと設定
        substitute_goal = obs['achieved_goal']
    obs = next_obs

for replay_action in action_list:
    next_obs, reward, done, info = env.step(replay_action)
    # 擬似ゴールに対する報酬の取得
    substitute_reward = env.compute_reward(
        obs['achieved_goal'], substitute_goal, info)
    # バッファに記録
    R.append((replay_action, obs['observation'], next_obs['observation'],
              substitute_reward, substitute_goal))
    obs = next_obs
```

上記のようにバッファRに記録をしたら、Rからミニバッチを取得してアルゴリズムのパラメータ更新を行う。


# 参考
[Hindsight Experience Replay 論文](https://arxiv.org/abs/1707.01495)
[Medium : Learning from mistakes with Hindsight Experience Replay](https://medium.com/@minsangkim/learning-from-mistakes-with-hindsight-experience-replay-547fce2b3305)
[Qiita : 物理エンジンmujoco登録の仕方](https://qiita.com/syo_s/items/88ffa540c2822b2a923c)
[Open AI Gym & MuJoCo を使う](http://blog.syundo.org/post/20180204-open-ai-gym/)

# おわり
３Dロボットカッコいいので、MuJoCoのライセンスを買おうかな、と思いました:blush:

記載に誤りがありましたら、ご指摘いただけたら嬉しいです。