Loading [MathJax]/jax/output/CommonHTML/jax.js

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

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

_금융덕후_ 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의 학습율(α), 0.9의 감가율(γ),
그리고 0.2의 입실론(ϵ)값을 가지고 학습합니다.
그리고 경험에 의해 학습될 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와 동일하게, 입실론 그리디 정책을 사용합니다.
입실론 그리디는 ϵ값에 의해 랜덤한 행동을,
1ϵ값에 의해 탐욕적으로 행동을 선택합니다.
(랜덤한 행동은 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
반응형