強化学習で上昇気流を捉える４

# 背景
[前回](https://qiita.com/goichiya/items/39d8343aa273a4b74156)の続き。
[参考書](https://amzn.to/3fXoAAh)の流れにあわさせてQlearningをやってみる。
強化学習でよく聞くモデルなのでちょっとテンションが上がっている

# 前提

[前回](https://qiita.com/goichiya/items/39d8343aa273a4b74156#%E5%89%8D%E6%8F%90)からちょっと変えて連続してサーマル(上昇気流)に乗っていることをゴールに変更
学習時の条件をパラグライダーで実際に達成したい条件に近づけた。

1. 一つのセルは六角形、セルの組み合わせで空域を構成
1. 設定した空域の真ん中付近にサーマルエリア（上昇気流帯）
1. 進行方向に向かって正面、右斜め前、左斜め前にしか進めない
1. 出発位置はランダムに決定
1. 次の進行方向は空域の外に出ないようにランダムに決定
1. サーマルに連続して4回留まればゴール


# Qlearning
ここまで順番にやってきていると、実装内容はそれほど変わるものではなかった。
全体の流れは以下。

1. 必要なライブラリのインポート
1. 空域の広さ、サーマル(上昇気流)の場所、今の進行方向に対して取り得る次の進行方向などを定義
1. パイロットの初期化、動き方を定義したメソッド、動いた結果による報酬の計算を定義（表示をヘックスにしたいので余分な計算も入っている）
1. 表示関連の処理を定義
1. エピソードの定義（train,predict双方で使うので中で処理を分岐。predictの時は学習せずに結果だけ使って(結果で得たpolicyだけ使って）エピソードを進めてゆく)
1. policyの更新処理を定義
1. trainの処理を定義（取得するepsode数やepsilonの値は引数で与える）
1. 実行して結果を表示！

## 空域のイメージ

大戦略とかシミュレーションゲームで見るような6角形のセルで平面を表現
今まで通り9x9、上昇気流は中央付近の7セル(赤線内)
![air_space_thermal.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328600/9e87fd25-f22a-692f-835a-5989c4788d41.jpeg)

## 状態、行動、ポリシー、行動―状態価値関数、報酬
- 状態は現在地と現時点での進行方向を表現
`(x,y,進行方向)`

- 行動はセルの辺の方向に向かって動く(6方向)
`[0, 1, 2, 3, 4, 5]`

- 今の進行方向に対して、次に選択できる行動正面、左斜め前、右斜め前に限定される
`actions = {0:[5,0,1],1:[0,1,2],2:[1,2,3],3:[2,3,4],4:[3,4,5],5:[4,5,0]}`

- ポリシーは状態をキーにして設定
`{(x,y,進行方向):ポリシー}`

- 行動―状態価値関数は状態、次の行動をキーに設定
`{(x,y,進行方向),次の進行方向):行動―状態価値関数}`

行動―状態価値関数を初期化する際に、進行方向に対して取り得ない次の進行方向についてはすごく小さな値を設定しておく。取り得る次の進行方向については0で初期化することで見学習のルートの学習を促す。

- 上昇気流に入った場合の報酬は1、
- 上昇気流に関係ない移動の報酬は -1（移動先が空域外に出る場合はその場にとどまり、ポリシーを更新)

## get_episode

trainの場合はepisodeの終了を待たず、逐次的に行動―状態価値関数が計算されてpolicyを更新するため、学習処理がget_episode内に取り込まれコードがスリムになってように思える。

```python:get_episode
def get_episode(pilot,epsilon,train,s_p=(2,2,2),a_p=2):
   # trainの場合はスタート地点をランダムに決定して学習を進める
   # trainでない場合は渡された位置から既存のポリシーに従って進める（epsilo=0で学習結果の確認）
    
    episode = []
    # 最初の状態をランダムに決定
    # statusの形で初期のstateを決定
    x = np.random.randint(-4,5)
    y = np.random.randint(-4,5)
    direc = np.random.randint(0,6)

    s = (int(x),int(y),int(direc))
    # 初期状態フラグ
    initial = True

    while True:
    # 初期状態の場合
        if initial:
            if train:
                cand_direc = actions[s[2]]
                a = npp.random.choice(cand_direc)
                initial = False
            else:
                s=s_p
                a=a_p
                initial = False
        else:
            # epsilon-greedy
            if np.random.random() < epsilon:
                cand_direc = actions[s[2]]
                a =  npp.random.choice(cand_direc)
            else:
                a = pilot.policy[s]
        r, s_new = pilot.fly(s,int(a))
        episode.append((s,a,r))        
        # トレーニング状態を判別することで、学習時と予測時を切り分ける
        if train:
            pilot.q[(s,a)] += 0.2 * (r + pilot.q[(s_new,pilot.policy[s_new])] - pilot.q[(s,a)])
            policy_update(pilot,s)        
#         連続して４回上昇気流に入っていたらブレーク
        if pilot.stay_cnt>=4:
            break        
        s=s_new
    return episode
```

# 結果
出た！前回同様アクションごとの出力を見てみる
![Inkedqlearning_202106204541774551_qlearning(1000) value direc_add.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328600/39c138d5-8e38-3b7c-e98d-9b769b86715b.jpeg)

ところどころ直観的に？という部分もあるものの(赤枠）、取り得ない方向を選択してはいないので経路として通っていないことが原因かもしれない。(取りうる方向は0で初期化、それ以外はすごく小さい値で初期化したので前回のように取り得ない方向がポリシーと採用されることはなくなった）

上手く動いてそうなので、学習回数を変えながら状態(3,1,0)、次の行動が0方向を起点にしたルートの予測結果を比較してみる

100回(Wall time: 1.27 s)
端っこに行ってスタックしてしまった、、
![Inkedqlearning_predict train(100)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328600/17000d37-e0f3-6cdc-2f1d-732050cccd98.jpeg)

1,000回(Wall time: 6.21 s)
おぉ、それっぽく到達してサーマルを回り始めている
![Inkedqlearning_predict train(1000)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328600/c0abe674-d79b-0954-6c4e-9dcac7035a28.jpeg)

10,000回(Wall time: 55.7 s)
なぜ大回りしたし、、、
![Inkedqlearning_predict train(10000)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328600/6c8258d4-3d5c-3916-fb62-f3fd04e41f33.jpeg)

100,000回(9min 21s)
それっぽいルートになった。結果としては1,000回の時と一緒
ここまでやってみると1,000回の結果は偶々だったのか？と思ってしまう。
この設定だとおそらく今のがベストであろうと見た目でわかるが、もっと複雑な状態だと「これで十分！」と確信するには学習回数を増やしても結果が変わらなくなるまで学習し続けるしかないのか？そこで、200,000回実行して結果を確認したところ、100,000回と同じルートになった。
![Inkedqlearning_predict train(100000)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328600/e1a18396-b485-f5a0-8074-145935ea2fe4.jpeg)

# このあと

だんだんイメージしていた強化学習っぽくなってきて、起点を変えながら嬉々として遊んでいる。
今は200,000回試行しても20分程度で済んでいるものの、空域を広げたりすると計算資源は相応に必要になってくるので、応用してゆく際にはパフォーマンス面をもう少し見たほうが良いかも。どうあっても1回は成功するルートを探し出さないと計算が有効に進むことはないという点が教師ありとか教師なしとかでも感じた力業感に似ている

[前回]
(https://qiita.com/goichiya/items/6fd3e746d25bddce23de#%E3%81%93%E3%81%AE%E3%81%82%E3%81%A8)の物に足したり引いたり
- ~~なぜこうなったのかもう少し考える、、(新)~~
- ~~進行方向を考慮した表現もなんとかしたい(新)~~
- ~~できればベストの方向を表示させたい~~
- もっと強化学習っぽいアルゴリズムを試してみる
- ~~サーマルエリアに4回以上滞空出来たらゴールとしたい（サーマルエリアを突っ切って出てしまわないように~~
- いつの日か実際の距離感に合わせてみたい
- そしてさらにいつの日か携帯デバイスとして飛びながらベストな方向を教えてくれるようにしたい

# 参考
参考書の流れに従って次はニューラルネットワークする予定
[ITエンジニアのための強化学習理論入門](https://amzn.to/3fXoAAh)
環境やライブラリは執筆時点とコ異なっているので、gpu,tensorflowあたりを乗り越えられるかが最初の関門の気がしている。