軽く動く強化学習、基本のQ学習からニューラルネットやLSTMの応用まで、4種のエージェントと7種のタスク

# はじめに

[「ScratchでAIを学ぼう」](https://www.amazon.co.jp/gp/product/4296106945)の筆者です。この本では、初めての方でもきちんと分かることを目指し、[ゲーム](https://scratch.mit.edu/projects/400073856/)を題材にしてQ学習を3段階で説明しました。

ここでは、その続編として、**python**で基本の**Q学習**、**ニューラルネット**の応用、また、多少実験的な試みになりますが、短期記憶ユニットである**LSTM/GRU**の応用を紹介したいと思います。

使用するプログラム[**memoryRL**](https://github.com/itoshin-tech/memoryRL)は、GitHubからダウンロードできます。GPUのない普通のPCでも短時間（長くても数分）で学習できることを想定しています。

まず、2つの例を見てください。

1つ目の例はmany_swamp と呼んでいるタスクです。ロボットを操作して、4つのゴール（青）を全て巡回できるとクリアとなり、エピソードが終わります。エピソード毎に壁とゴールの配置がランダムに変わります。アニメーションは、**ニューラルネットを使ったQ学習**(qnet)で学習させた結果です。

![20201212_qnet_many_30000.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/1d36f42b-0ccd-0a46-413b-3959470612e8.gif)

アニメーションの左側が環境全体を表しています。白黒の中央と右側の図は、実際に強化学習アルゴリズムが受け取る観測情報に対応します。観測情報は、2マス以内の周囲のゴール（中央）と壁（左）のパターンになります。

ロボットは2マスまでの周囲しか見えない設定なのですが、「壁はよける」、「ゴールに近づいたらそちらに曲がる」、「何もなければまっすぐ進む」、のようなアルゴリズムで動いているように見えます。この**アルゴリズム自体を強化学習が作った**ということです。マップはランダムに変わりますが、追加学習をすることなく対応しています。

ちなみに、この学習には、筆者の古いノートPCでも**4分弱**で終わりました (qnet, many_swamp, 33000 qstep, 3分40秒)。
※筆者のノートPCのCPUは、Intel Core i7-6600UでGPUなし。i7でも第6世代なので結構遅く、i5-7500Uの方が上のようです。 [参考：CPU性能比較表 | 最新から定番のCPUまで簡単に比較](https://pcrecommend.com/cpu/)


2つめの例は、Tmaze_either と呼んでいるタスクです。青のゴールにたどり着けばクリアなのですが、ゴールが観測情報から消えてしまいます。観測から消えてもゴールはあるので、ゴールの位置を覚えておいて正しい方向にＴ字路を曲がる必要があります。つまり**短期記憶が必要**なタスクです。

![20201229_gru_either.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/30cf62a2-5c6e-06ee-2f87-df8acf345c9d.gif)

このような問題は通常のQ学習ではできませんが、短期記憶ユニットである**LSTMを組み入れたQ学習**で解くことが可能となりました。

以上が2つの例の紹介になりますが、このmemoryRLプロジェクトでは、このタスクを含む7つのバリエーションのタスクを、4種類の強化学習エージェントで試すことができます。

この記事ではmemryRLの使い方をメインに紹介しますが、学習の原理、実装方法など、更に詳しい解説は、以下にまとめていますので参考にしてください。
http://itoshi.main.jp/tech/0100-rl/rl_introduction/


# 環境構築

ここでは、windows 10 を想定し、pythonでmemoryRLを動かすための環境構築を説明します。

## Anaconda インストール
まず、pythonをするための基本環境として、anaconda をインストールします。

以下の記事が参考になります。
<a href="https://www.python.jp/install/anaconda/windows/install.html">Windows版Anacondaのインストール</a>

## 仮想環境作成
次に、特定のpythonのバージョンやモジュールのバージョンの環境を設定するために、conda で仮想環境を作成します。

仮想環境について詳しく知るためには以下が参考になります。
<a href="https://qiita.com/ozaki_physics/items/985188feb92570e5b82d">【初心者向け】Anacondaで仮想環境を作ってみる</a>
<a href="https://www.python.jp/install/anaconda/conda.html">python japan, Conda コマンド</a>

スタートメニューからAnaconda Powershell Prompt を立ち上げます。

以下のコマンドで、mRLという名前の仮想環境をpython3.6 で作成します（mRLでなくてもよいですが、ここではmRLとして進めていきます）。

```
(base)> conda create -n mRL python=3.6
```

以下で、mRLをアクティベートします（仮想環境に入る）。

```
(base)> conda activate mRL
```


tensorflow, numpy, h5py をバージョン指定でインストールします。

```
(mRL)> pip install tensorflow==1.12.0 numpy==1.16.1 h5py==2.10.0
```

opencv-pythonとmatplotlib は、最新のバージョンでインストールします。

```
(mRL)> pip install opencv-python matplotlib
```

今後、この仮想環境 mRL に入ることで(> conda activate mRL)、今インストールしたモジュール（ライブラリ）の環境で、プログラムを動かすことができます。

# memoryRLのダウンロードと展開

gitが使える方は、以下のコマンドでクローンすれば完了です。

```
(mRL)> git clone https://github.com/itoshin-tech/memoryRL.git
```

以下、git を使っていない人用の説明です。

ブラウザで、次ののURLに行きます。
https://github.com/itoshin-tech/memoryRL

Code から、Download zip を選び、PCの適当な場所に保存して解凍すると、memoryRL-master というフォルダが作られます

![github.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/f0f8e154-449b-22ca-0401-c7550afa3264.jpeg)

これで準備ＯＫです。

# memoryRLの実行

memoryRLを解凍して作成されたフォルダーに入ります。

```
(mRL)> cd C:\解凍した位置のパス\memoryRL-master\  
```

sim_swanptour.py を以下のコマンドで実行します。

```
(mRL)> python sim_swanptour.py
```

すると以下のように使い方が表示されます。

```
---- 使い方 ---------------------------------------
3つのパラメータを指定して実行します

> python sim_swanptour.py [agt_type] [task_type] [process_type]

[agt_type]      : q, qnet, lstm, gru
[task_type]     :silent_ruin, open_field, many_swamp, 
Tmaze_both, Tmaze_either, ruin_1swamp, ruin_2swamp, 
[process_type]  :learn/L, more/M, graph/G, anime/A
例 > python sim_swanptour.py q open_field L
---------------------------------------------------
```

説明にあるように、python sim_swanptour.py の後に3つのパラメータをセットして使います。

agt_type とtask_type については後ほど図解しますが、パラメータの説明は以下のようになります。


|[agt_type]|強化学習のアルゴリズム|
|---|---|
|q         |Q学習|
|qnet         |ニューラルネットを使ったQ学習|
|lstm         |LSTMを使った短期記憶付きQ学習|
|gru         |GRUを使った短期記憶付きQ学習|

|[task_type]|タスクのタイプ。<br> 全てのタスクにおいて全ての青いゴールに辿り着けばクリア|
|---|---|
|silent_ruin|マップ固定、ゴール数2。|
|open_field|壁なし、ゴール数1。ゴールの位置はランダムに変わる。|
|many_swamp|壁あり、ゴール数4。配置がランダムに変わる。|
|Tmaze_both|T迷路。短期記憶が必要。|
|Tmaze_either|T迷路。短期記憶が必要。|
|ruin_1swamp|壁あり、ゴール数1。|
|ruin_2swamp|壁あり、ゴール数2。高難易度。|

|[process type]|プロセスの種類|
|---|---|
|learn/L|初めから学習する|
|more/M|追加学習をする|
|graph/G|学習曲線を表示する|
|anime/A|タスクを解いている様子をアニメーションで表示|

では、具体的に、<strong class="marker-yellow">qnet</strong> (ニューラルネットを使ったQ学習) に <strong class="marker-yellow">many_swamp</strong> のタスクを学習させる場合を説明します。

学習を開始するので、最後のパラメータは、<strong class="marker-yellow">more か L</strong>にします。

```
(mRL)> python sim_swanptour.py qnet many_swamp L
```

すると、以下のようにコンソールに学習過程の評価が表示され、全5000 stepの学習が行われます。

```
qnet many_swamp  1000 --- 5 sec, eval_rwd -3.19, eval_steps  30.00
qnet many_swamp  2000 --- 9 sec, eval_rwd -0.67, eval_steps  28.17
qnet many_swamp  3000 --- 14 sec, eval_rwd -0.21, eval_steps  26.59
qnet many_swamp  4000 --- 18 sec, eval_rwd -1.27, eval_steps  28.72
qnet many_swamp  5000 --- 23 sec, eval_rwd -1.29, eval_steps  28.90
```

1000回に1回、**評価**のプロセスがあり、そこで、eval_rwdとeval_stepが計算されます。eval_rwd は、その時の1エピソード中の平均報酬、eval_steps は平均step数です。評価プロセスでは、学習を停止し、行動選択のノイズも0にした状態で、100回のエピソードを行って平均を出しています。

eval_rwdやeval_step を指標として学習が目標値に進んだときにも学習は終了するようになっています（EARY_STOP）。目標値はタスク毎に設定されています。

最後に以下のような学習過程のグラフ（eval_rwd, eval_steps）が表示されます。
[q]を押すとグラフが消え終了します。

![qnet_5000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/9a064390-ae47-9efc-3f46-b028c426f8f4.png)


学習の結果後の<strong class="marker-yellow">動作アニメーションを見る</strong>には、最後のパラメータを<strong class="marker-yellow">anime か A</strong>にします。

```
(mRL)> python sim_swanptour.py qnet many_swamp A
```

すると、以下のようなアニメーションが表示されます。

![20201212_qnet_many_5000.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/b9d37b40-741b-e0af-6f47-f9a44ce840f3.gif)

100エピソードが終わると終了します。[q]を押すと途中終了します。


アニメーションを見ると、適切に動けていなことが分かります。まだ学習が足りないのです（アニメーションの中央から右側の白黒の図はエージェントへの入力を表しています）。

そこで、<strong class="marker-yellow">追加学習</strong>します。最後のパラメータを<strong class="marker-yellow">more か M</strong>にして実行します（初めから学習する場合は L を使います）。

```
(mRL)> python sim_swanptour.py qnet many_swamp M
```
　
このコマンドを数回繰り返します。EARY_STOPが表示されて途中終了すると、学習が良いところまで進んだといえます。many_swampは、eval_rwd が1.4以上または、eval_steps が22以下になるとEARY_STOPとなります。

```
qnet many_swamp  1000 --- 5 sec, eval_rwd  0.55, eval_steps  24.25
qnet many_swamp  2000 --- 9 sec, eval_rwd  0.92, eval_steps  23.52
qnet many_swamp  3000 --- 14 sec, eval_rwd  1.76, eval_steps  21.69
EARY_STOP_STEP 22 >= 21
```

グラフが最後に表示されます。エピソード当たりの報酬(rwd)が増加し、ステップ数(Steps)が減少していることから、学習が進んでいたことが分かります。

![qnet_30000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/08d04c5a-2e14-431c-4cb3-9867161cd031.png)

アニメーションを見てみましょう。

```
(mRL)> python sim_swanptour.py qnet many_swamp A
```

たまに失敗しますが、だいたいうまくいっているようです。

![20201212_qnet_many_30000.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/8ba0fe7e-7556-f816-3297-22cd2a6ac617.gif)

今までに学習させた<strong class="marker-yellow">グラフを表示</strong>するには、最後のパラメータを<strong class="marker-yellow">graph か G</strong>にします。

```
(mRL)> python sim_swanptour.py qnet many_swamp G
```

以上がsim_swamptour.py（池巡り）の使い方の説明です。

# 強化学習アルゴリズムの種類

[agt_type] で指定できる強化学習アルゴリズム（エージェント）は、 q, qnet, lstm, gru の4種類です。ここではその特徴を簡単に説明します。

## q：通常のQ学習 
基本の<strong class="marker-yellow">Q学習アルゴリズム</strong>です。各観察に対して各行動のQ値を変数（Qテーブル）に保存し、更新していきます。観測値は500個まで登録できる設定にしています。それ以上のパターンが観測されたらメモリーオーバーで強制終了となります。

![agt_q.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/314b3df8-8f4b-a1bf-0bb1-2a2413240574.png)

## qnet：ニューラルネットを使ったQ学習アルゴリズム
<strong class="marker-yellow">ニューラルネットワーク</strong>でQ値を出力するよう学習します。入力は観測値で、出力は3つ値です。この3つの値が各行動のQ値に対応します。中間層は64個のReLUユニットです。
未知の観測値に対してもQ値を出力することができます。

![agt_qnet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/b3a07179-c2a0-2e9e-eb00-7c9457c1e712.png)


## lstm/gru: 記憶ユニットを使ったQ学習アルゴリズム

過去の入力にも依存した反応が可能な<strong class="marker-yellow">記憶ユニット（LSTM または GRU）</strong>を加えたモデルです。LSTMを使ったアルゴリズムをlstm、GRUを使ったアルゴリズムをgruとしています。

qやqnetは、観測値が同じであれば同じ行動しか出力することしかできませんが、lstmやgruはモデルは、<strong class="marker-yellow">過去の入力が異なれば今の観測値が同じでも異なる行動を出力することが原理的に可能</strong>です。

LSTMは自然言語処理のモデルでもよく使用されている記憶ユニットです。GRUはLSTMをシンプルにしたモデルです。

![agt_lstm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/0ea618bb-0b69-9690-1862-10cf95b2939a.png)

LeRU を64個、LSTMまたはGRU を32個を使用しています。

# タスクの種類
[task_type] で指定できるタスクは、silent_ruin, open_field, many_swamp, Tmaze_both, Tmaze_either, ruin_1swamp, ruin2swamp の7種類です。ここではその特徴を簡単に説明します。

## 全タスクで共通のルール

全てのタスクで共通しているのは、ロボットが<strong class="marker-yellow">全てのゴール（青のマス）に訪れたらクリア</strong>、というルールです。

アルゴリズムが受け取る情報は、<strong class="marker-yellow">ロボットを中心とした、限られた視野におけるゴールと壁の情報</strong>です。各タスクの図の、右側の白黒の図がその情報に対応します。

報酬は、初めてのゴールにたどり着くと+1.0、壁に当たると-0.2、それ以外のステップで-0.1となります。

## silent_ruin

ゴールは2か所ありますが、マップは常に同じです。そのために、観測のバリエーションは限られており、q でも学習が可能です。

![silent_ruin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/e71c31bb-66ca-b6c2-ccea-237ce0e4c426.png)

## open_field

壁はありませんが、ゴールの場所はエピソード毎にランダムに変わります。しかし、壁がないので観測のバリエーションは限られており、qでも学習が可能です。

![open_field.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/bb25cf65-597d-29d2-5170-c09d832c7483.png)

## many_swamp

ゴールと壁の位置がエピソード毎にランダムに決まるために、観測のバリエーションが多く、qではメモリーオーバーとなってしまい学習ができません。qnet での学習が可能です。

![many_swamp.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/ddb0cf81-18fa-9955-c267-552e2f83c8b0.jpeg)

## Tmaze_both

普通の強化学習ではできない問題です。

マップは固定ですが、片方のゴールにたどり着くと、スタート地点に戻されます。そして、訪れたゴールも見えたままです。この状態で、次は別な方のゴールに進まなければなりません。

スタート地点にロボットがいるとき、スタート直後でも、片方のゴールに訪れた後でも同じ観測となります。そのために、同じ観測に対して同じ行動しか選べないqやqnet は、適切な行動を学習種ることができません。過去の履歴で行動を変えることができる <strong class="marker-yellow">gru と lstm のみが学習可能</strong>です。

![Tmaze_both.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/5db68879-2abd-d274-007c-3ed319ffba1d.png)

## Tmaze_either

このTmazeは、左右のどちらかでゴールが出現しますが、2ステップ後にゴールが見えなくなります。

ロボットは2ステップでT迷路の分岐路に来ることになりますが、ゴールが見えていた方を覚えておき、そちらに向かうことが必要です。このタスクも、qやqnet ではできません。<strong class="marker-yellow">gruとlstmのみが学習可能</strong>です。

![Tmaze_eigher.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/5f1aafa5-ccd8-4cad-d573-61e93633a050.png)

## ruin_1swamp

壁が8個、ゴールは1つで、配置がランダムに変わります。回り込んでゴールにたどり着かなければならない場合もあり難易度は高めです。

![ruin_1swamp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/1e47e3ad-d1dc-4400-5736-349b3c32c07b.png)

## ruin_2swamp

最高難易度のタスクです。ゴールは2つで、片方にたどり着くとスタート地点に戻されます。ゴールは訪れても消えません。Tmaze_both のランダムバージョンのようなタスクです。gruやlstmでも満足にはできませんでした。

ゴールを更に増やしたりフィールドを大きくすることで、更に難易度を上げることができます。将来このようなタスクでもしっかり解けるような強化学習アルゴリズムを開発したいものです。

![ruin_2swamp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326704/f6f58cfe-02b9-a3ff-eafd-f22d508aa811.png)

# 参考
この記事ではmemryRLの使い方をメインに紹介しましたが、学習の原理、実装方法など、更に詳しい解説は、以下にまとめていますので参考にしてください。
http://itoshi.main.jp/tech/0100-rl/rl_introduction/