SARSA Learning
강화 학습(Reinforcement Learning)에서 사용하는 SARSA에 대해서 알아 보고 Gym에서 제공하는 문제를 해결하기 위한 알고리듬을 만들어 보자.
실제로 돌려 보고 싶으면 구글 코랩으로 ~
문제 (Problem)
👤 보스
몬테 카를로와 같이 한 판이 끝날때 까지 기다리지 않고
한 칸 앞으로만 가도 학습이 되는게 있다고 하네?
아래 체육관(Gym)에 가서
‘얼음 호수8X8’ 문제를 그걸로 풀어 보게
⚙️ 엔지니어
또 다른 알고리듬이 등장 하겠군…
배움에는 끝이 없구냥…
문제 분석 (Problem Anaysis)
일단 Gym을 돌려 보자!
import gym
import numpy as np
from time import sleep
from IPython.display import display, clear_output, Pretty
#
# Environment
#
env = gym.make('FrozenLake8x8-v0', is_slippery=False) # 얼음위에서 미끄러지지 않도록 설정
state = env.reset()
# Initial world display
world = env.render(mode='ansi')
display(Pretty(world))
sleep(0.5)
#
# Agent
#
for step in range(100):
action =env.action_space.sample()
next_state, reward, done, info = env.step(action)
state = next_state
# updated world display
world = env.render(mode='ansi')
clear_output(wait=True)
display(Pretty(world))
sleep(0.5)
if done: # an episode finished
print("Episode finished after {} timesteps".format(step+1))
break
(Right)
SFFFFFFF
FFFFFFFF
FFF[41mH[0mFFFF
FFFFFHFF
FFFHFFFF
FHHFFFHF
FHFFHFHF
FFFHFFFG
Episode finished after 7 timesteps
환경 (Environment)
‘얼음호수8X8’ 의 환경은 64개의 상태(State)와 4개의 액션(Action)으로 구성 되어 있다.
액션 (Action)
env.action_space
Discrete(4)
‘얼음호수8X8’ 에서는 4개의 액션이 존재한다. 그리고 각각의 액션이 번호로 지정 되어 있다.
\(A = \{0, 1, 2, 3\}\)
Num | Action |
---|---|
0 | 왼쪽으로 이동 (Move Left) |
1 | 아래로 이동 (Move Down) |
2 | 오른쪽으로 이동 (Move Right) |
3 | 위로 이동 (Move Up) |
상태 (State)
env.observation_space
Discrete(64)
‘얼음호수8X8’의 상태(State) \(S\)는 다음과 같이 각 상태가 0과 63까지 번호로 지정되어 있다.
\(S = \{0, 1, \cdots , 63\}\)
\(\begin{vmatrix} 0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 \\ 8 & 9 & 10 & 11 & 12 & 13 & 14 & 15 \\ 16 & 17 & 18 & 19 & 20 & 21 & 22 & 23 \\ 24 & 25 & 26 & 27 & 28 & 29 & 30 & 31 \\ 32 & 33 & 34 & 35 & 36 & 37 & 38 & 39 \\ 40 & 41 & 42 & 43 & 44 & 45 & 46 & 47 \\ 48 & 49 & 50 & 51 & 52 & 53 & 54 & 55 \\ 56 & 57 & 58 & 59 & 60 & 61 & 62 & 63 \end{vmatrix}\)
그리고 각 상태마다 액션(action), 확률(probability), 다음 상태(next state), 보상(reward), 종료(done)가 {action: [(probability, nextstate, reward, done)]}
형식으로 정의 되어 있다.
에피소드가 종료되는 상태를 기록해 놓자
\(S_{\text{terminate}} = \{19, 29, 35, 41, 42, 49, 52, 54, 59, 63\}\)
보상 (Reward)
55번의 상태를 까보자
env.P[55]
{0: [(1.0, 54, 0.0, True)],
1: [(1.0, 63, 1.0, True)],
2: [(1.0, 55, 0.0, False)],
3: [(1.0, 47, 0.0, False)]}
63번 상태로 이동하면 \(R = 1.0\) 을 얻고 에피소드가 끝이난다.
에이전트 (Agent)
에이전트는 환경에서 부터 주어진 상태(\(S\)), 액션(\(A\)), 보상(\(R\))을 가지고 가치(Q-value)를 계산하고 정책(Policy)을 수립해서 최적의 액션을 수행해야 한다.
정책 (Policy)
에이전트가 주어진 상태에서 액션을 선택하는 확률이다.
\(\pi(s,a) = \pi (a|s) = \mathbb P[A_t = a | S_t = s]\)
상태 가치 (State Value)
상태 가치는 상태 s 에서 기대되는 미래 보상의 합이다. 여기서 \(G_t\)는 벨만 방정식(Bellman Equation)에 의해서 보상(Reward)값과 다음 스텝의 상태 가치로 분해 할 수 있다.
\(\begin{align} v_{\pi}(s) & = \mathbb E_{\pi} [G_t | S_t = s] \\ & = \mathbb E_{\pi} [R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \cdots | S_t = s] \\ & = \mathbb E_{\pi} [R_{t+1} + \gamma (R_{t+2} + \gamma R_{t+3} + \cdots) | S_t = s] \\ & = \mathbb E_{\pi} [R_{t+1} + \gamma G_{t+1} | S_t = s] \\ & = \mathbb E_{\pi} [R_{t+1} + \gamma v_{\pi}(S_{t+1}) | S_t = s] \end{align}\)
Q-value (State Action Value)
Q-value는 상태 s 에서 액션 a 를 수행할 경우에 기대되는 미래 보상의 합이다. 여기서 \(G_t\)는 벨만 방정식(Bellman Equation)에 의해서 보상(Reward)값과 다음 스텝의 Q-value로 분해 할 수 있다.
\(\begin{align} q_{\pi}(s,a) & = \mathbb E_{\pi} [G_t | S_t = s, A_t = a] \\ & = \mathbb E_{\pi} [R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \cdots | S_t = s, A_t = a] \\ & = \mathbb E_{\pi} [R_{t+1} + \gamma (R_{t+2} + \gamma R_{t+3} + \cdots) | S_t = s, A_t = a] \\ & = \mathbb E_{\pi} [R_{t+1} + \gamma G_{t+1} | S_t = s, A_t = a] \\ &= \mathbb E_{\pi} [R_{t+1} + \gamma q_{\pi}(S_{t+1}, A_{t+1}) | S_t = s, A_t = a] \end{align}\)
최적 가치 (Optimal Value)
상태 s 에서 가장 큰 Q-value이다.
\(q_*(s,a) = \max_{\pi} q_{\pi}(s,a)\)
최적 정책 (Optimal Policy)
\(q_*(s,a)\)가 결정 되면 \(\pi (a|s) = 1\) 이 된다. 즉 상태 s일때는 100% 액션 a를 수행 한다.
\(\pi_*(s,a) = \begin{cases} 1 & \text{if } a= \text{argmax}_{a \in A} q_\star(s,a) \\ 0 & \text{otherwise} \end{cases}\)
⚙️ 엔지니어
에이전트는
각 상태(state)의 액션(action)마다
Q-value를 계산한다.몬테 카를로 방법은
에피소드가 끝나고 나서 \(G_t\)를 이용해서 Q-value를 계산했다.그런데
\(G_t\) 대신에 \(R_{t+1} + \gamma q_{\pi}(S_{t+1}, A_{t+1})\)을 이용하면
한 스텝만 이동해도 Q-value를 계산할 수 있는
신묘한 알고리듬이 된다.
그것은 바로…
살사 (SARSA)
State-Action-Reward-State-Action의 줄임말로서 미래 보상의 합 \(G_t\) 대신에 보상(Reward)값과 다음 스텝의 Q-value를 이용해서 Q-value를 최적화 하는 알고리듬이다.
\(Q(S_t, A_t) \leftarrow Q(S_t, A_t) + \alpha \left( R_{t+1} + \gamma Q(S_{t+1}, A_{t+1}) - Q(S_t, A_t) \right)\)
\(\alpha\) : learning rate
\(\gamma\) : 디스카운트 (discount factor)
Target
목표로 하는 값이다. 여기서 타겟은 \(R_{t+1} + \gamma Q(S_{t+1}, A_{t+1})\)이다.
Error (델타)
목표값과 현재값과의 차이를 \(\delta\) 라고 한다.
\(\delta_t = R_{t+1} + \gamma Q(S_{t+1}, A_{t+1}) - Q(S_t, A_t)\)
계속해서 에피소드를 실행 시키면서 \(Q(S_t, A_t)\)에다가 \(\alpha * \delta_t\) 를 업데이트 하면 결국 \(Q(s, a) \rightarrow q_*(s,a)\)가 된다.
𝜀-greedy
다음 스텝의 액션을 선택하는 경우에 욕심쟁이(greedy)처럼 최고의 Q-value의 액션만을 선택하면 오류가 발생할 수 있다. 따라서 적당히 램덤하게 액션을 선택하는 경우를 추가한 것이 𝜀-greedy이다.
\(\pi \leftarrow \epsilon \text{-greedy(Q)}\)
𝜀의 확률로 랜덤 액션을 수행하고 (1-𝜀)의 확률로 Q-value가 가장 큰 액션을 수행한다.
학습 (Learning)
살사를 이용해서 최적의 Q-value를 찾아보자!
from tqdm import tqdm
num_state = env.observation_space.n
num_action = env.action_space.n
num_episode = 1000
# Initialize Q_table
Q_table = np.random.uniform(low=0.0, high=0.00000001, size=(num_state, num_action))
# Zero for terminate states
for s in [19, 29, 35, 41, 42, 49, 52, 54, 59, 63]:
Q_table[s] = 0
for episode in tqdm(range(num_episode)):
state = env.reset()
done = False
# Hyper parameter
epsilon = 0.3
alpha = 0.1
gamma = 0.9
if np.random.uniform() < epsilon:
action = env.action_space.sample()
else:
action = np.argmax(Q_table[state])
while not done:
next_state, reward, done, info = env.step(action)
if np.random.uniform() < epsilon:
next_action = env.action_space.sample()
else:
next_action = np.argmax(Q_table[next_state])
target = reward + gamma*Q_table[next_state, next_action]
delta = target - Q_table[state][action]
Q_table[state][action] += alpha * delta
state, action = next_state, next_action
100%|██████████| 1000/1000 [00:00<00:00, 3752.20it/s]
해결 (Solution)
state = env.reset()
done = False
# Initial world display
world = env.render(mode='ansi')
display(Pretty(world))
sleep(0.5)
while not done:
action = np.argmax(Q_table[state]) # Optimal Policy
state, reward, done, info = env.step(action)
# updated world display
world = env.render(mode='ansi')
clear_output(wait=True)
display(Pretty(world))
sleep(0.5)
if done and state == 63:
print('\n 🎉👍 성공! 🍺🥇')
(Down)
SFFFFFFF
FFFFFFFF
FFFHFFFF
FFFFFHFF
FFFHFFFF
FHHFFFHF
FHFFHFHF
FFFHFFF[41mG[0m
🎉👍 성공! 🍺🥇