mujocoのタスクで最高クラスの性能を誇るTD3をchainerで実装する

# TD3って何?
[TD3](https://arxiv.org/pdf/1802.09477.pdf)(Twin Delayed Deep Deterministic policy gradient)は2018年10月時点で他の強化学習アルゴリズムと比べて
mujocoの複数のタスクで圧倒的な性能を叩きだした強化学習アルゴリズムです。
本記事ではTD3の解説と[chainerでTD3のアルゴリズムを実装してみた](https://github.com/yuishihara/chainer-td3)ので簡単に紹介します。
こんな感じです↓
![walker2d_v2_result](https://raw.githubusercontent.com/yuishihara/chainer-td3/master/trained_results/mujoco/walker2d-v2/result.gif)

# TD3アルゴリズムの概要
TD3はDeep Deterministic policy gradientと名前にあるとおり、[Deep deterministic policy gradient](http://proceedings.mlr.press/v32/silver14.pdf)(通称DDPG)を改善したアルゴリズムです。
そのため、TD3について説明するために簡単にDDPGについて説明してから、TD3について説明します。

## Deep Deterministic Policy Gradient(DDPG)とは
DDPGは[2014年にSilverらによって提案](http://proceedings.mlr.press/v32/silver14.pdf)された強化学習アルゴリズムで、決定的方策の勾配が次のように計算できることを利用して、最適方策を求めることを目指します。

```math
\nabla_{\theta}J_{\theta} = \mathbb{E}\bigl[\nabla_{\theta}\pi_{\theta}(s)\nabla_{a}Q^{\pi}(s, a)|_{a=\pi_{\theta}(s)}\bigr]
```

これは、それまでに一般的だった次の確率的方策の勾配とは異なり、

```math
\nabla_{\theta}J_{\theta} = \mathbb{E}\bigl[\nabla_{\theta}\log\pi_{\theta}(a|s)Q(s, a)\bigr]
```

行動価値関数の行動に対する勾配と方策のパラメータに対する勾配の積から計算されます。さらに、この事実は2014年まで知られておらず、
その理由の1つとして、確率的方策の勾配を決定的方策の勾配にそのまま持って行こうとすると、$\pi_{\theta}=1$のため、$\nabla\log\pi_{\theta}$が0になってしまうことから、そのような勾配は存在しないのではと思われていたことが考えられます。

## DDPGからTD3へ

DDPGの登場で決定的方策の勾配を求めて最適な方策を計算することができ、これにより、連続値を取るような行動の学習が容易になりました。しかしながら、DDPGはハイパーパラメータの設定が難しく学習が困難という問題と、行動価値関数を過大に評価してしまう問題があり、使い勝手がよくありませんでした。TD3はこのあたりの問題を解決します。ここでは、後者の過大に評価してしまう問題について簡単に解説します。

### 価値の過大評価

Q学習ではQ値の推定にmax演算子が絡むことにより、推定値が過大に評価されてしまう問題があり、それが[Double Q Learning](https://arxiv.org/abs/1509.06461)の提案へとつながりました。DDPGにはmax演算子は出てきませんが、勾配の計算の結果から、似たような問題があることがTD3の論文内で指摘されています。具体的には次の真の勾配と推定値を使った勾配を見比べることにより、過大評価されていることを知ることができます。

```math
\phi_{approx} = \phi + \frac{\alpha}{Z_{1}}\mathbb{E}\bigl[\nabla_{\phi}{\pi_{\phi}}(s)\nabla_{a}Q_{\theta}(s, a)|_{a=\pi_{\phi}(s)}\bigr] \\
\phi_{true} = \phi + \frac{\alpha}{Z_{2}}\mathbb{E}\bigl[\nabla_{\phi}{\pi_{\phi}}(s)\nabla_{a}Q^{\pi}(s, a)|_{a=\pi_{\phi}(s)}\bigr]
```

上の式の1つめは、推定しているパラメータ化された行動価値関数による方策の勾配を正規化したうえでパラメータを更新する場合の式、2つめの式は真の行動価値関数による方策の勾配を正規化してパラメータを更新する場合の式です。
方策の勾配はそれぞれの関数に対してパラメータを目標に向かって更新するときに一番傾きが大きい方向を指しているはずなので、完全に同じ関数出ない限り、1つめと2つめの式で更新された結果のパラメータ$\phi_{approx}$と$\phi_{true}$は違う値になります。
そのため、$\phi_{approx}$と$\phi_{true}$を使った更新後の方策を$\pi_{approx}$、$\pi_{true}$とすると、それぞれの価値観数に対して、非常に小さい更新の範囲では次の関係性が成り立ちます。

```math
\mathbb{E}\bigl[Q_{\theta}(s, \pi_{approx}(s))\bigr] \geq \mathbb{E}\bigl[Q_{\theta}(s, \pi_{true}(s))\bigr] \\
\mathbb{E}\bigl[Q^{\pi}(s, \pi_{true}(s))\bigr] \geq \mathbb{E}\bigl[Q^{\pi}(s, \pi_{approx}(s))\bigr]
```

このとき、なんらかの理由で推定中の行動価値関数が真の行動価値関数に対して次のような関係になっている場合、

```math
\mathbb{E}\bigl[Q_{\theta}(s, \pi_{true}(s))\bigr] \geq \mathbb{E}\bigl[Q^{\pi}(s, \pi_{true}(s))\bigr]
```

これまでの結果から、推定値が次のように過大評価されることになります。

```math
\mathbb{E}\bigl[Q_{\theta}(s, \pi_{approx}(s))\bigr] \geq \mathbb{E}\bigl[Q^{\pi}(s, \pi_{approx}(s))\bigr]
```

実際にこのような状況になるか、もしくはこれが実用上問題になるかはこのままではわからないですが、TD3ではこれが事実であることが実験的に示されています。そして、それの解決法として、Double Q-learningのような更新方法で値を更新することで、この問題に対処しています。

# TD3の実装

chainerでの全実装は[GitHubに上げた](https://github.com/yuishihara/chainer-td3)のでそちらをご覧ください。ここでは簡単にアルゴリズムの実装のポイントとなる部分を紹介します。

## パラメータの更新

TD3では学習を安定化させるために、DQNと同様に学習対象のネットワークとはべつに目標値を出力するネットワークを用意し、次のように少しずつ目標値出力のネットワークパラメータを学習対象のネットワークのものに一致させていきます。

```math
\theta_{target} \leftarrow \tau\theta + (1-\tau)\theta_{target}
```

これはchainerでは次のようにして実装することができます。

```python
def _update_target_network(self, target, origin, tau):
    for target_param, origin_param in zip(target.params(), origin.params()):
        target_param.data = tau * origin_param.data + (1.0 - tau) * target_param.data |
```

## 決定的方策の勾配計算

下記の式を実装するときのイメージがわかりにくいのですが、chainerでは次のようにして、方策のネットワークの出力を行動価値観数のネットワークの入力に入れたものをlossとして誤差逆伝搬するだけで、実は簡単に計算できます。

```math
\nabla_{\theta}J_{\theta} = \mathbb{E}\bigl[\nabla_{\theta}\pi_{\theta}(s)\nabla_{a}Q^{\pi}(s, a)|_{a=\pi_{\theta}(s)}\bigr]
```

```python
a = self._pi(s_current)
q1 = self._q1(s_current, a)

pi_loss = -F.mean(q1)

self._pi_optimizer.target.cleargrads()
pi_loss.backward()
pi_loss.unchain_backward()
self._pi_optimizer.update()
```

# 結果

最後にTD3でmujocoでいくつかのタスクを学習させてみたので、結果をここに貼っておきます。

## Walker2d-v2

|result|score|
|:---:|:---:|
| ![walker2d-v2-result](https://raw.githubusercontent.com/yuishihara/chainer-td3/master/trained_results/mujoco/walker2d-v2/result.gif) |![walker2d-v2-graph](https://raw.githubusercontent.com/yuishihara/chainer-td3/master/trained_results/mujoco/walker2d-v2/result.png)|

## Ant-v2

```sh
$ python3 main.py --test-run --pi-params=trained_results/mujoco/ant-v2/pi_final_model
```

|result|score|
|:---:|:---:|
| ![ant-result](https://raw.githubusercontent.com/yuishihara/chainer-td3/master/trained_results/mujoco/ant-v2/result.gif) |![ant-v2-graph](https://raw.githubusercontent.com/yuishihara/chainer-td3/master/trained_results/mujoco/ant-v2/result.png)|