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

강화학습 - (20-1) Q러닝 코드예제

_금융덕후_ 2020. 11. 5. 16:28
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
 

환경

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

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

Q러닝 에이전트

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

In [2]:
class QLearning:
    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)
    
    ...
 

입실론 그리디 정책

Q러닝 에이전트는 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)를 학습합니다.
여기서 SARSA 에이전트와 다른점은, TD에러를 계산할 때 다음 Q값의 max를 취해준 다는 것입니다.

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 = max(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
 

환경 & 에이전트 초기화

환경과 Q러닝 에이전트를 초기화 합니다.

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
episode: 4840, rewards: 0
episode: 4860, rewards: 0
episode: 4880, rewards: 0.622
episode: 4900, rewards: 0
episode: 4920, rewards: 0
episode: 4940, rewards: 0.514
episode: 4960, rewards: 0
episode: 4980, rewards: 0
episode: 5000, rewards: 0.784
 

학습된 Q값 출력

에이전트가 학습한 각 상태/행동별 Q값을 출력해봅니다.
이 때 dict의 key 수는 상태의 수, 즉 grid의 수가 되고,
각 key 별로 행동 3개의 리스트를 가지게 됩니다.
지난 포스팅의 SARSA의 Q값과 많이 다른 것을 확인할 수 있습니다.
SARSA는 사용된 Q값만 학습되기 때문에 왼쪽/오른쪽 회전의 Q값이 모두 0이었던 반면,
Q러닝은 최대 Q값을 고려하기 때문에, 각 grid에서 외전 방향까지 Q값이 존재하게 됩니다.

In [6]:
{s:np.round(q, 5).tolist() for s, q in agent.q_values.items()}
Out[6]:
{0: [0.03904, 0.03912, 0.04478],
 3: [0.02265, 0.02196, 0.03316],
 12: [0.06329, 0.06366, 0.07137],
 15: [0.01526, 0.01904, 0.09523],
 27: [0.03885, 0.05522, 0.20192],
 6: [0.02418, 0.02352, 0.03446],
 24: [0.0869, 0.08699, 0.09592],
 36: [0.10339, 0.10325, 0.11115],
 18: [0.03366, 0.02723, 0.12812],
 30: [0.06034, 0.04337, 0.2774],
 39: [0.36232, 0.35877, 0.43248],
 9: [0.03522, 0.03522, 0.03727],
 42: [0.46285, 0.47469, 0.57318],
 21: [0.12882, 0.12123, 0.15318],
 33: [0.23449, 0.2584, 0.34039],
 45: [0.0, 0.0, 0.0]}
 

에피소드 시각화

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

In [7]:
show_video()
 
 
 

학습 곡선 시각화

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

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]
In [11]:
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.legend()
Out[11]:
<matplotlib.legend.Legend at 0x7f636c16dc18>
 
728x90
반응형