데이터사이언스/강화학습

강화학습 - (20-2) Expected SARSA 코드예제

Johnny Yoon 2020. 11. 15. 21:35
728x90
반응형

강화학습

 

패키지 설치

다음 코드는 세가지 패키지가 선행 되어야 합니다.

sudo apt-get install ffmpeg
pip install gym
pip install gym_minigrid

In [1]:
import numpy as np
import pandas as pd
import random
from collections import defaultdict
import gym
import gym_minigrid
import matplotlib.pyplot as plt
%matplotlib inline
 

환경

예제 코드는 그리드월드 예제에서 Expected SARSA 에이전트가 학습하는 코드 입니다.
에이전트가 최종 지점에 도달하면 보상을 받고,
에이전트가 취할 수 있는 행동은 다음 세가지 입니다.

  1. 왼쪽으로 회전
  2. 오른쪽으로 회전
  3. 전진
 

Expected SARSA 에이전트

Expected SARSA 에이전트 클래스를 만들어 줍니다. (설정은 SARSA 및 Q러닝과 모두 동일)
Expected SARSA에이전트는 0.01의 학습율($\alpha$), 0.9의 감가율($\gamma$),
그리고 0.2의 입실론($\epsilon$)값을 가지고 학습합니다.
그리고 경험에 의해 학습될 q값을 저장합니다.

In [2]:
class ExpectedSARSA:
    def __init__(self, actions, agent_indicator=10):
        self.actions = actions
        self.agent_indicator = agent_indicator
        self.alpha = 0.01
        self.gamma = 0.9
        self.epsilon = 0.2
        self.q_values = defaultdict(lambda: [0.0] * actions)
    
    ...
 

입실론 그리디 정책

Expected SARSA 에이전트는 SARSA와 동일하게, 입실론 그리디 정책을 사용합니다.
입실론 그리디는 $\epsilon$값에 의해 랜덤한 행동을,
$1 - \epsilon$값에 의해 탐욕적으로 행동을 선택합니다.
(랜덤한 행동은 np.random.choice, 탐욕적 행동은 np.argmax에 의해 선정)

In [3]:
    ...
    
    def act(self, state):
        if np.random.rand() < self.epsilon:
            action = np.random.choice(self.actions)
        else:
            state = self._convert_state(state)
            q_values = self.q_values[state]
            action = np.argmax(q_values)
        return action
    
    ...
 

Q값 학습

현재상태 (S, state), 현재행동 (A, action), 보상 (R, reward),
다음상태 (S', next_state), 다음행동 (A', next_action)
위 다섯가지 원소를 가지고 시간차(TD)를 학습합니다.
여기서 Q러닝과는 다르게 Expected SARSA는 max가 아닌 mean으로 다음 Q값을 산정합니다.

In [3]:
    ...
    
    def update(self, state, action, reward, next_state, next_action):
        state = self._convert_state(state)
        next_state = self._convert_state(next_state)
        
        q_value = self.q_values[state][action]
        next_q_value = sum(self.q_values[next_state]) / len(self.q_values[next_state])
        
        td_error = reward + self.gamma * next_q_value - q_value
        self.q_values[state][action] = q_value + self.alpha * td_error
 

환경 & 에이전트 초기화

환경과 Expected SARSA 에이전트를 초기화 합니다.

In [4]:
env = gen_wrapped_env('MiniGrid-Empty-6x6-v0')
obs = env.reset()

agent_position = obs[0]

agent = QLearning(3, agent_position)
 

에피소드 학습

5000번의 에피소드를 통해 학습합니다.
각 20번의 에피소드마다 리워드 값을 출력합니다.

In [5]:
rewards = []
for ep in range(5000):
    done = False
    obs = env.reset()
    action = agent.act(obs)
    
    ep_rewards = 0
    while not done:
        next_obs, reward, done, info = env.step(action)

        next_action = agent.act(next_obs)

        agent.update(obs, action, reward, next_obs, next_action)
        
        ep_rewards += reward
        obs = next_obs
        action = next_action
    rewards.append(ep_rewards)
    if (ep+1) % 20 == 0:
        print("episode: {}, rewards: {}".format(ep+1, ep_rewards))
env.close()
 
episode: 20, rewards: 0
episode: 40, rewards: 0
episode: 60, rewards: 0
episode: 80, rewards: 0
episode: 100, rewards: 0
episode: 120, rewards: 0
episode: 140, rewards: 0
episode: 160, rewards: 0
episode: 180, rewards: 0
episode: 200, rewards: 0
(생략)...
episode: 4800, rewards: 0
episode: 4820, rewards: 0.874
episode: 4840, rewards: 0
episode: 4860, rewards: 0
episode: 4880, rewards: 0.802
episode: 4900, rewards: 0.766
episode: 4920, rewards: 0.45999999999999996
episode: 4940, rewards: 0.18999999999999995
episode: 4960, rewards: 0.262
episode: 4980, rewards: 0.17199999999999993
episode: 5000, rewards: 0
 

학습된 Q값 출력

에이전트가 학습한 각 상태/행동별 Q값을 출력해봅니다.
이 때 dict의 key 수는 상태의 수, 즉 grid의 수가 되고,
각 key 별로 행동 3개의 리스트를 가지게 됩니다.
SARSA와는 다르게 모든 상태-행동 페어에 대해 Q값이 산정된 것을 볼 수 있습니다.
또 Q러닝과는 다르게 모든 Q값이 Q러닝에서보다는 조금 낮게 형성된 것을 볼 수 있는데,
이는 max가 아닌 mean을 사용하기 때문입니다.

In [6]:
{s:np.round(q, 5).tolist() for s, q in agent.q_values.items()}
Out[6]:
{0: [0.00024, 0.00023, 0.00037],
 12: [0.00085, 0.00082, 0.00204],
 24: [0.00259, 0.00267, 0.00573],
 27: [0.00233, 0.00201, 0.02306],
 39: [0.06328, 0.06452, 0.16756],
 3: [3e-05, 3e-05, 0.00014],
 15: [0.00017, 0.00018, 0.00232],
 18: [0.00064, 0.00083, 0.00977],
 6: [0.00012, 0.00012, 0.00052],
 9: [0.00079, 0.00079, 0.0012],
 36: [0.0085, 0.00851, 0.01059],
 21: [0.00882, 0.00881, 0.02552],
 33: [0.06586, 0.06369, 0.20937],
 30: [0.00653, 0.00703, 0.08427],
 45: [0.0, 0.0, 0.0],
 42: [0.21, 0.20227, 0.37398]}
 

에피소드 시각화

다음은 최종 에피소드의 영상입니다.

In [7]:
show_video()
 
 

학습 곡선 시각화

다음은 학습 시 받은 보상의 이동 평균(Moving Average)을 시각화 한 그래프 입니다.
비교 분석을 위해 SARSA 및 Q러닝의 결과가 포함되었고,
세가지의 기법 모두5번의 시도 중 가장 좋은 그래프를 시각화 하였습니다.
결과에서 볼 수 있는 것과 같이, SARSA보다는 수렴 속도가 조금 더 빠르지만,
Q러닝보다는 훨씬 떨어지는 것을 볼 수 있습니다.

In [9]:
sarsa_logs = pd.read_csv('./logs/rewards_sarsa.csv', index_col=False).iloc[:, 1]
q_logs = pd.read_csv('./logs/rewards_qlearning.csv', index_col=False).iloc[:, 1]
exp_sarsa_logs = pd.read_csv('./logs/rewards_expectedsarsa.csv', index_col=False).iloc[:, 1]
In [10]:
plt.figure(figsize=(16, 8))
plt.plot(q_logs.cumsum() / (pd.Series(np.arange(q_logs.shape[0]))+1), label="QLearning")
plt.plot(sarsa_logs.cumsum() / (pd.Series(np.arange(sarsa_logs.shape[0]))+1), label="SARSA")
plt.plot(exp_sarsa_logs.cumsum() / (pd.Series(np.arange(exp_sarsa_logs.shape[0]))+1), label="ExpectedSARSA")
plt.legend()
Out[10]:
<matplotlib.legend.Legend at 0x7fcb638c4f60>
 
728x90
반응형