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

# 背景
[前回](https://qiita.com/goichiya/items/6fd3e746d25bddce23de#%E8%83%8C%E6%99%AF)の続き。
参考書も同じで、対象はサンプリングによる状態価値関数の評価

# 前提
[前回](https://qiita.com/goichiya/items/6fd3e746d25bddce23de#%E5%89%8D%E6%8F%90)からちょっと修正

1. セルは六角形
1. 進行方向に向かって正面、右斜め前、左斜め前にしか進めない
1. 設定した空域の真ん中付近にサーマルエリア（上昇気流帯）
1. 出発位置はランダムに決定
1. サーマルにたどり着いたらゴール
1. 次の進行方向は空域の外に出ないようにランダムに決定
1. エピソード内で指定回数試行しても終了しない場合は打ち切り

# サンプリングによる価値反復法?
エピソード内で次に進む方向をランダムに選択し、より良いポリシーを探す方法と理解したものの、終わる気配がない時があった。
エピソード内のデータを見てみると同じところをぐるぐる回ってしまっていることが原因だったので、エピソード内の移動上限を設定して打ち切るようにしなければならなかった（このエピソードはポリシーの決定に寄与しない）

```python:stateの構造
(x座標,y座標,進行方向)
```

```python:policyの構造
{(x座標,y座標,進行方向):次の進行方向}
```

```python:行動-状態価値関数(q)の構造
{((x座標,y座標,進行方向),次の進行方向):q}
```
ゴールの辿り着いたエピソードで各状態の行動-状態価値関数を出して、一番良い値を次の行動

```python:Pilotの動きと報酬
      # 次の進行方向が現在の進行方向から選択できる進行方向の場合
      if a in actions[s[2]]:
        # stateを更新
        # Y軸が偶数の場合、右上はX軸＋1、Y軸＋1
        # Y軸が奇数の場合、右上はX軸＋0、Y軸＋1
        if s[1]%2==0:
            x_shift_right=1
            x_shift_left=0
        else:
            x_shift_right=0
            x_shift_left=1

        if rnd_direc==0:
            s_new=(s[0]+x_shift_right,s[1]+1,a)
        elif rnd_direc==1:
            s_new=(s[0]+1,s[1],a)
        elif rnd_direc==2:
            s_new=(s[0]+x_shift_right,s[1]-1,a)
        elif rnd_direc==3:
            s_new=(s[0]-x_shift_left,s[1]-1,a)
        elif rnd_direc==4:
            s_new=(s[0]-1,s[1],a)
        elif rnd_direc==5:
            s_new=(s[0]-x_shift_left,s[1]+1,a)

        # 空域にいなくなった場合、場所を動かず報酬は−１
        # ポリシーとしての次のアクションが変わらないと抜け出せないのでpolicyを取り直し
        if s_new not in self.states:
            # 取りうる進行方向の取得
            possible_act = actions[s[2]]
            # 今回採用し、抜けせないことが判明した方向を取りうる進行方向のリストから引く
            reduced_act = list(set(possible_act) - set([a]))
            # ポリシーとして持っている次への進行方向を更新する
            new_policy =np.random.choice(reduced_act,1)
            self.policy[s] = int(new_policy)
            return -1, s
        # 上昇気流に入った場合、移動して報酬は１
        elif (s_new[0],s_new[1]) in upstreams:
            return 1, s_new
        # # 上記のどの場合でもない場合、報酬はマイナスで移動
        return -1, s_new
# 　　#　 次の進行方向が現在の進行方向から選択できない進行方向の場合
      else:
        # 現在地から動かず
        return -1, s
```

```python:get_episode
def get_episode(pilot):
  global global_episode_stop_cnt
  global global_episode_stop_max
  episode = []
  # 最初の状態をランダムに決定
  # statusの形で初期のstateを決定(choicesを使わない！)
  # cupyのrandintは2つ目の数値を含まない[low,high)
  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:
      # cupyのrandintは2つ目の数値を含まない模様[low,high)
      a = int(np.random.randint(0, 6))
      initial = False
    else:
      a = pilot.policy[s]
    r, s_new = pilot.fly(s,int(a))
    episode.append((s,a,r))
    # 新しい状態が上昇気流に入った場合ブレーク
    if (s_new[0],s_new[1]) in upstreams:
      break
    s=s_new
    # episodeがmaxを超えても終了しない場合、強制終了
    global_episode_stop_cnt += 1
    if global_episode_stop_cnt > global_episode_stop_max:
      global_episode_stop_cnt = 0
      break
  return episode
```

# 結果
進行方向が0(右斜め上）の場合の結果

試行回数4000000回
![policy_202106145108976000_sampling(4000000) value direc(0).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328600/79358243-7472-4f42-9872-2ddd3e1a0f67.png)

試行回数5000000回
![policy_202106141147762488_sampling(5000001) value direc(0).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328600/3947aed6-6bf2-b5c7-a113-7b9879794ba4.png)

座標(-4.5,-1)の値などちょっとだけ異なるところがあるものの、おおむね一致している。
全体の結果は直観的に正しそう。
- 進行方向が0(右斜め上）なので、左下のエリアはそのまま右斜め上か右方向を目指す
- 左上のエリアは動ける範囲で右の方（1の方向）を目指す。
- 右下のエリアは動ける範囲で左の方(5の方向）を目指す
- 右上のエリアも動ける範囲で左の方(5の方向）を目指す

あれ、現在の進行方向は０なのに座標(3,4)の値は進めない方向の3なんだ、、
現在の進行方向から選択できない次の進行方向が選ばれるのは(3,4)が出発点の時だけで、その場合は一回ペナルティを受けた後、初期値で設定された選択可能な次の進行方向（3つの中からランダムに設定）に進み始めるので、ゴールができないわけではない。サンプリング数を増やしても結果は変わらない

![policy_202106143141911058_sampling(6000000) value direc(0).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328600/ebff4b09-ab7f-1341-abd3-e83300186570.png)

とりあえず起点と進行方向をランダムに決めたあと、次の進行方向を選択可能な範囲からランダムに選択するようにする。

```python:get_episode(修正）
def get_episode(pilot):
  global global_episode_stop_cnt
  global global_episode_stop_max
  episode = []
  # 最初の状態をランダムに決定
  # statusの形で初期のstateを決定(choicesを使わない！)
  # cupyのrandintは2つ目の数値を含まない[low,high)
  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:
#     　決定したx,y,direcから選択可能なaをランダムに取得
        cand_direc = actions[s[2]]
        a = npp.random.choice(cand_direc)
        initial = False
    else:
      a = pilot.policy[s]
    r, s_new = pilot.fly(s,int(a))
    episode.append((s,a,r))
    # 新しい状態が上昇気流に入った場合ブレーク
    if (s_new[0],s_new[1]) in upstreams:
      break
    s=s_new
    # episodeがmaxを超えても終了しない場合、強制終了
    global_episode_stop_cnt += 1
    if global_episode_stop_cnt > global_episode_stop_max:
      global_episode_stop_cnt = 0
      break
  return episode
```

、、しかし結果は変わらなかった。あちこちにログを入れて見たところq_maxの初期値が-10**10なのに、各行動-状態価値関数(q)を0で初期化しているので、ゴールまでたどり着いたルート上にあるqよりも初期化しただけのqのほうが大きいと判断されることが原因だった（言葉にするとわかりにくい、、）ので、qを初期化時に-10**10とする

```python:初期化
        self.q = {}
        self.cnt = {}
        for s in self.states:
            for a in directions_list:
                self.q[(s,int(a))] =  -10**10
                self.cnt[(s,int(a))] = 0
```
これで直感的にあっている結果になった
![policy_202106155909468228_sampling(5000002) value direc(0).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328600/46c7bab9-6c0d-ff3c-f36c-093ff082e998.png)

# 気がついたこと
- cupyはnumpyとちょっと違うところがあった
    - cupyのrandom.choiceの引数が厳格だった
    - cupyのrandom.randintの上限、下限の定義が違った
- q_maxと報酬の与え方は整合してないとだめ
- 今回の方法で実行時間が１２分ぐらい。前回、前々回のように全部計算してしまう方法はもっと短時間で終わっていたのでこの方法が役に立つのはもっと大きな空域を設定した場合かもしれない
- 参考書でこのあとやっているεgreedyを適用するともっと早く終わり、結果はほぼ同じだった。一度成功したルートを利用して計算時間を短くするのは色々捗るが、最後までたどり着くルートを最初に探し当てられないと終わらないと思うとランダムに数を打つのも必要な気がする、、

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


# 参考
紙の本もkindleも買ってしまったが、やっぱり紙の本が好き
[ITエンジニアのための強化学習理論入門](https://amzn.to/3fXoAAh)
理論もコードも丁寧に解説されていて素敵