つくりながら学ぶ！深層強化学習_1

#深層強化学習　〜Pytorchによる実践プログラミング〜

理系大学院修士1年のはりまです。
自分の学習内容をメモ程度にまとめていきます。見辛いのは申し訳ありません。
分からないところは教えていただきたいです。

---

実装コード（GitHub）
https://github.com/YutaroOgawa/Deep-Reinforcement-Learning-Book

---

## Chap.2 迷路課題に強化学習を実装しよう

### 2.1 Pythonの使い方

### 2.2 迷路とエージェントを実装

```ruby
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
```

```ruby
fig=plt.figure(figsize=(5,5))
ax=plt.gca()


plt.plot([1,1],[0,1],color='red',linewidth=2)
plt.plot([1,2],[2,2],color='red',linewidth=2)
plt.plot([2,2],[2,1],color='red',linewidth=2)
plt.plot([2,3],[1,1],color='red',linewidth=2)


plt.text(0.5,2.5,'S0',size=14,ha='center')
plt.text(1.5,2.5,'S1',size=14,ha='center')
plt.text(2.5,2.5,'S2',size=14,ha='center')
plt.text(0.5,1.5,'S3',size=14,ha='center')
plt.text(1.5,1.5,'S4',size=14,ha='center')
plt.text(2.5,1.5,'S5',size=14,ha='center')
plt.text(0.5,0.5,'S6',size=14,ha='center')
plt.text(1.5,0.5,'S7',size=14,ha='center')
plt.text(2.5,0.5,'S8',size=14,ha='center')
plt.text(0.5,2.3,'START',ha='center')

plt.text(2.5,0.3,'GOAL',ha='center')


ax.set_xlim(0,3)
ax.set_ylim(0,3)
plt.tick_params(axis='both',which='both',bottom='off',top='off',
                labelbottom='off',right='off',left='off',labelleft='off')


line, =ax.plot([0.5],[2.5],marker="o",color='g',markersize=60)

```

![Screenshot from 2019-12-18 17-42-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546118/ef31f47c-c2b5-97c8-855c-ab8849402cdf.png)
>
>迷路の全体図ですね。
>



・エージェントがどのように行動するのかを定めたルールのことを**方策（Policy）**と呼ぶ

・$\pi_\theta(s,a)$と表す

・状態$s$のときに行動$a$を採用する確率はパラメータ$\theta$で決まる方策$\pi$に従う


```ruby
theta_0 = np.array([[np.nan, 1, 1, np.nan],
                    [np.nan, 1, np.nan, 1],
                    [np.nan, np.nan, 1, 1],
                    [1, 1, 1, np.nan],
                    [np.nan, np.nan, 1, 1],
                    [1, np.nan, np.nan, np.nan],
                    [1, np.nan, np.nan, np.nan],
                    [1, 1, np.nan, np.nan]
                    ])
```

・パラメータ$\theta_0$を変換して方策$\pi_\theta(s,a)$を求める

```ruby

def simple_convert_into_pi_fron_theta(theta):

     [m,n] = theta.shape
     pi = np.zeros((m,n))
     for i in range(0,m):
         pi[i, :] = theta[i, :] / np.nansum(theta[i, :])

     pi = np.nan_to_num(pi)

     return pi
```


```ruby
pi_0 = simple_convert_into_pi_fron_theta(theta_0)
```
・壁方向に進む確率は０

・その他の方向へは等確率で移動

```ruby
pi_0
```

![Screenshot from 2019-12-18 17-43-53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546118/49035677-530e-b02f-51f5-a7b9e666b398.png)



・初期方策が完成したので、方策$\pi_{\theta_{0}}(s,a)$に従ってエージェントを動かす

・ゴールにたどり着くまでエージェントを移動させ続ける


```ruby
def get_next_s(pi, s):
    direction = ["up", "right", "down", "left"]

    next_direction = np.random.choice(direction, p=pi[s, :])

    if next_direction == "up":
        s_next = s - 3
    elif next_direction == "right":
        s_next = s + 1
    elif next_direction == "down":
        s_next = s + 3
    elif next_direction == "left":
        s_next = s - 1

    return s_next
```

```ruby
def goal_maze(pi):
    s = 0
    state_history = [0]

    while (1):
        next_s = get_next_s(pi, s)
        state_history.append(next_s)

        if next_s == 8:
            break
        else:
            s = next_s

    return state_history
```

・ゴールするまでどのような軌跡で、合計何ステップしたのかを確認

```ruby
state_history = goal_maze(pi_0)
```


```ruby
print(state_history)
print("迷路を解くのにかかったステップ数は" + str(len(state_history) - 1) + "です")
```

![Screenshot from 2019-12-18 17-45-49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546118/825b32ec-f801-3cbd-0ef5-109d154e53bf.png)



・状態遷移の軌跡を可視化


```ruby
from matplotlib import animation
from IPython.display import HTML


def init():
    line.set_data([], [])
    return (line,)


def animate(i):
    state = state_history[i]
    x = (state % 3) + 0.5
    y = 2.5 - int(state / 3)
    line.set_data(x, y)
    return (line,)


anim = animation.FuncAnimation(fig, animate, init_func=init, frames=len(
    state_history), interval=200, repeat=False)

HTML(anim.to_jshtml())
```

![2_2_maze_random.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546118/3613414f-acdc-9b19-ffce-f145876d9f6a.gif)




### 2.3 方策反復法の実装

・エージェントが一直線にゴールへ向かうように方策を学習させる方法を考える

####（１）方策反復法
うまくいったケースの行動を重要視する作戦

####（２）価値反復法
ゴール以外の位置（状態）にも価値（優先度）をつける作戦





```ruby
def softmax_convert_into_pi_from_theta(theta):

    beta = 1.0
    [m, n] = theta.shape
    pi = np.zeros((m, n))

    exp_theta = np.exp(beta * theta)

    for i in range(0, m):

        pi[i, :] = exp_theta[i, :] / np.nansum(exp_theta[i, :])

    pi = np.nan_to_num(pi)

    return pi
```

・方策$\pi_{{\theta_0}}$を求める


```ruby
pi_0 = softmax_convert_into_pi_from_theta(theta_0)
print(pi_0)
```

![Screenshot from 2019-12-18 18-14-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546118/725d9bbd-87b8-f295-6bf3-340eb8467beb.png)



・2.2で扱った、”get_next_s”関数を修正

・状態だけでなく、採用した行動も取得


```ruby
def get_action_and_next_s(pi, s):
    direction = ["up", "right", "down", "left"]
    next_direction = np.random.choice(direction, p=pi[s, :])

    if next_direction == "up":
        action = 0
        s_next = s - 3
    elif next_direction == "right":
        action = 1
        s_next = s + 1
    elif next_direction == "down":
        action = 2
        s_next = s + 3
    elif next_direction == "left":
        action = 3
        s_next = s - 1

    return [action, s_next]
```

・ゴールにたどり着くまでエージェントを動かす"goal_maze"関数も修正


```ruby
def goal_maze_ret_s_a(pi):
    s = 0
    s_a_history = [[0, np.nan]]

    while (1):
        [action, next_s] = get_action_and_next_s(pi, s)
        s_a_history[-1][1] = action

        s_a_history.append([next_s, np.nan])

        if next_s == 8:
            break
        else:
            s = next_s

    return s_a_history
```

```ruby
s_a_history = goal_maze_ret_s_a(pi_0)
print(s_a_history)
print("迷路を解くのにかかったステップ数は" + str(len(s_a_history) - 1) + "です")
```

![Screenshot from 2019-12-18 18-15-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546118/890febde-6bfe-0066-d8ff-f3c4dbd8b545.png)

>
>長いので省略・・・・
>


#### 方策勾配法に従って、方策を更新

・方策勾配法は、以下の式に従ってパラメータ$\theta$を更新する

```math

\theta_{s_i,a_j}=\theta_{s_i,a_j}+\eta*\Delta\theta_{s,a_j} \\
\Delta\theta{s,a_j}=\{ N(s_i,a_j)-P(s_i,a_j)N(s_i,a) \}/T


```


```ruby
def update_theta(theta, pi, s_a_history):
    eta = 0.1
    T = len(s_a_history) - 1

    [m, n] = theta.shape
    delta_theta = theta.copy()

    for i in range(0, m):
        for j in range(0, n):
            if not(np.isnan(theta[i, j])):

                SA_i = [SA for SA in s_a_history if SA[0] == i]

                SA_ij = [SA for SA in s_a_history if SA == [i, j]]

                N_i = len(SA_i)
                N_ij = len(SA_ij)

                delta_theta[i, j] = (N_ij - pi[i, j] * N_i) / T

    new_theta = theta + eta * delta_theta

    return new_theta
```

> - ここがマジでわからない！！！！
>
>```ruby
>new_theta = theta + eta * delta_theta
>```
> - なんで足し算！？
> - 試行回数が多い（最短経路である可能性が低い）ものは、引くべきでは？
> - 教えてください・・・・


・パラメータ$\theta$を更新し、方策$\pi_{\theta}$の変化を観察







```ruby
new_theta = update_theta(theta_0, pi_0, s_a_history)
pi = softmax_convert_into_pi_from_theta(new_theta)
print(pi)
```

![Screenshot from 2019-12-18 18-16-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546118/d5b0786f-d83f-972e-3342-016063993ea1.png)



・迷路を一直線にクリア出来るまで迷路内の探索とパラメータ$\theta$の更新を繰り返す

・方策$\pi$の変化の絶対値和が$10^{-4}$よりも小さくなったら終了


```ruby
stop_epsilon = 10**-4

theta = theta_0
pi = pi_0

is_continue = True
count = 1
while is_continue:
    s_a_history = goal_maze_ret_s_a(pi)
    new_theta = update_theta(theta, pi, s_a_history)
    new_pi = softmax_convert_into_pi_from_theta(new_theta)

    if np.sum(np.abs(new_pi - pi)) < stop_epsilon:
        is_continue = False
    else:
        theta = new_theta
        pi = new_pi
```

>
>本当は関数内に"print"があったんだけど、めんどいからカット・・・
>


```ruby
np.set_printoptions(precision=3, suppress=True)
print(pi)
```

![Screenshot from 2019-12-18 18-18-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546118/da9b70ff-d314-e0ef-5f6b-61efb589c236.png)



・可視化してみる


```ruby
from matplotlib import animation
from IPython.display import HTML


def init():
    line.set_data([], [])
    return (line,)


def animate(i):
    state = s_a_history[i][0]
    x = (state % 3) + 0.5
    y = 2.5 - int(state / 3)
    line.set_data(x, y)
    return (line,)

anim = animation.FuncAnimation(fig, animate, init_func=init, frames=len(
    s_a_history), interval=200, repeat=False)

HTML(anim.to_jshtml())
```

![2_3_maze_reinforce.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546118/702eb022-5383-452b-c420-35b13fd65147.gif)



・softmax関数は、パラメータ$\theta$が負の値になっても方策を導出できる

・方策勾配定理を用いると、パラメータ$\theta$の更新方法を、方策勾配法で解くことが可能

・方策勾配定理を近似的に実装するアルゴリズムREINFORCEが存在する


---

 - 今回はわからない点が発生してしまいました。
 - どなたか教えていただけると幸いです。