【dqn】LSTMで深層強化学習する♬

前回はカオスな運動を深層強化学習したが、どうも予測したり学習したモデルの新規運動への適用が不明だった。

そこで、少し馴染みなモデルであるLSTMを使って同じような深層強化学習ができないか、調査した。
その結果、以下のサイトの記事がまんまなので少し学習してCartpoleに適用してみた。

【参考】
・[強化学習にLSTMを組み込む](https://qiita.com/shiibass/items/5e62e1d0ae716824734b)

###やったこと
（１）参考サイトの再現
（２）Cartpoleに適用する～LSTMのパラメータ


###（１）参考サイトの再現
参考サイトの記事のまんま再現してみた。
参考サイトでは、[script.ipynb](https://github.com/shiibashi/qiita/blob/master/5/script.ipynb)を公開していて、これがそのまま動かせる。
※今回は議論しないが、このコードはdqnのお作法的なコードで読みやすく、ほかのものを作ろうとしたとき参考になると思う
ただし、コードはscriptなので解説とも少しことなるので、[python仕様](https://github.com/MuAuan/dqn/blob/master/dqn_lstm.py)に変更した。
そして、このコードを使ってLSTMのパラメータやモデルの構造を変更してどのように予測能力が変わるのかを測定してみた。
結果は以下のとおり
1や3のように、極端にパラメータ数が少ないと得点率は低めだが、５０個以上では９０％程度と高くなる。

|hidden|perfect|get|ratio|
|:-:|:-:|:-:|:-:|
| 1   | 328.  | 274.  | 83.5%  |
| 3   | 337.  | 288.  | 85.5%  |
| 8   | 329.  | 294.  | 89.1%  |
| 16  | 345.  | 298.  | 86.5%  |
| 32  | 326.  | 287.  | 87.8%  |
| 50  | 335.  | 303.  | 90.4%  |
| 100 | 328.  | 294.  | 89.7%  |

このLSTMへの変換で一番大切なことは、以下のコードの二行目です。


```py
model = Sequential()
model.add(Reshape(observation_space.shape,
          input_shape=(1,)+observation_space.shape))
model.add(LSTM(50, input_shape=(3, 2), 
          return_sequences=False,
          dropout=0.0))
model.add(Dense(n_action))
model.add(Activation('linear'))
```
つまり、最初のReshapeで入力shapeをinput_shape=(None,3, 2)とし、LSTMの３次元の入力に合わせています。
ここ大切なので、Reshapeの参考を示しておきます。
【参考】
・[Docs » レイヤー » CoreレイヤーのReshape] (https://keras.io/ja/layers/core/)
モデル全体は以下のようになっています。

```
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
reshape_1 (Reshape)          (None, 3, 2)              0
_________________________________________________________________
lstm_1 (LSTM)                (None, 50)                10600
_________________________________________________________________
dense_1 (Dense)              (None, 3)                 153
_________________________________________________________________
activation_1 (Activation)    (None, 3)                 0
=================================================================
Total params: 10,753
Trainable params: 10,753
Non-trainable params: 0
_________________________________________________________________
```
ちなみに、もともとの普通のモデルでも一定の得点率になりました。

```
model = Sequential()
model.add(Flatten(input_shape=(1,) + observation_space.shape))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(n_action))
model.add(Activation('linear'))
[1000 rows x 5 columns]
335.545417366 297.043598361　８８．５％
```
###（２）[Cartpoleに適用](https://github.com/MuAuan/dqn/blob/master/dqn_cartpole_lstm.py)する～LSTMのパラメータ
※コードは見出しからリンクしています
ここまで来ると他のフィッティングに適用するのは容易である。

つまり以下のようにコードに変更した。
因みに、batch_sizeとstatfulは文句言われたので削除。。。
肝心なことはここでも第二行目と三行目、そして四行目。入力サイズを調整しています。
三行目の出力Shapeはどれでも同じようにフィッティングしてくれました。
※三行目のReshape((2, 2)と四行目のinput_shape=(2, 2)を合わせます
※うまくやれば二行目は削除できるかもだけど、この方がウワンには分かりやすい

```py
model = Sequential()
model.add(Flatten(input_shape=(1,) + env.observation_space.shape))
model.add(Reshape((2, 2), input_shape=(4,)))
model.add(LSTM(100, input_shape=(2, 2), 
               return_sequences=False,
              dropout=0.0))
model.add(Dense(nb_actions))
model.add(Activation('linear'))
```

結果は、当然かもですが、LSTMのhiddenが大きければ、すべてカンプリート。つまりパラメータの選択とはあまり関係なく精度よく学習してくれました。

そして、以下のようにほぼ究極なパラメータでも一定のフィッティングができました。

```
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
flatten_1 (Flatten)          (None, 4)                 0
_________________________________________________________________
reshape_1 (Reshape)          (None, 1, 4)              0
_________________________________________________________________
lstm_1 (LSTM)                (None, 3)                 96
_________________________________________________________________
dense_1 (Dense)              (None, 2)                 8
_________________________________________________________________
activation_1 (Activation)    (None, 2)                 0
=================================================================
Total params: 104
Trainable params: 104
Non-trainable params: 0
_________________________________________________________________
```

```
 9973/10000: episode: 410, duration: 0.925s, episode steps: 40, steps per second: 43, episode reward: 40.000, mean reward: 1.000 [1.000, 1.000], mean action: 0.450 [0.000, 1.000], mean observation: -0.189 [-1.265, 0.756], loss: 2.688106, mean_absolute_error: 9.756250, mean_q: 18.575144
done, took 218.174 seconds
Testing for 5 episodes ...
Episode 1: reward: 83.000, steps: 83
Episode 2: reward: 65.000, steps: 65
Episode 3: reward: 55.000, steps: 55
Episode 4: reward: 94.000, steps: 94
Episode 5: reward: 116.000, steps: 116
```
以下はパラメータが最小で完全にフィッティングできた例です。

```
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
flatten_1 (Flatten)          (None, 4)                 0
_________________________________________________________________
reshape_1 (Reshape)          (None, 4, 1)              0
_________________________________________________________________
lstm_1 (LSTM)                (None, 2)                 32
_________________________________________________________________
dense_1 (Dense)              (None, 2)                 6
_________________________________________________________________
activation_1 (Activation)    (None, 2)                 0
=================================================================
Total params: 38
Trainable params: 38
Non-trainable params: 0
_________________________________________________________________
```

```
 9987/10000: episode: 419, duration: 0.601s, episode steps: 28, steps per second: 47, episode reward: 28.000, mean reward: 1.000 [1.000, 1.000], mean action: 0.464 [0.000, 1.000], mean observation: 0.113 [-0.565, 1.494], loss: 3.687102, mean_absolute_error: 8.641927, mean_q: 15.758093
done, took 261.193 seconds
Testing for 5 episodes ...
Episode 1: reward: 200.000, steps: 200
Episode 2: reward: 200.000, steps: 200
Episode 3: reward: 200.000, steps: 200
Episode 4: reward: 200.000, steps: 200
Episode 5: reward: 200.000, steps: 200
```
やはり、Cartpoleの出力は二択なのでLSTMの出力が二択でもOKなのでしょう。
ということで、dqnにLSTMを使えるようになりました。
###まとめ
・dqnにLSTMを使えるようになった
・フィッティングはかなり少ないパラメータでも可能である

・次回は、予測というか学習済パラメータの適用の仕方を見たいと思う
　※Cartpoleや今回学習した丁半ゲーム（仮称）はそうしているけどね。。。