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

강화학습 - (26-1) REINFORCE 코드예제

_금융덕후_ 2020. 12. 21. 23:58
728x90
반응형

 

 

 

 

강화학습

 

패키지 설치

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

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

gym.render() 코드가 에러를 발생할 경우, 다음 패키지를 설치하고:
sudo apt-get install xvfb

주피터 노트북을 다음 명령어를 통해 실행합니다:
xvfb-run -s "-screen 0 1400x900x24" jupyter notebook

In [1]:
import warnings; warnings.filterwarnings('ignore')
import numpy as np
import pandas as pd
import random
import gym
from gym import wrappers

import torch
import torch.nn as nn
import torch.optim as optim

import matplotlib.pyplot as plt
%matplotlib inline
 

환경

예제 코드는 Cart-Pole 예제에서 Expected SARSA 에이전트가 학습하는 코드 입니다.
Cart-Pole이란 막대를 최대한 오래 세워놓는 게임입니다.
Cart-Pole의 상태는 4차원 벡터로 각 값은 다음과 같은 의미를 갖습니다:

  • x : Cart의 가로상의 위치
  • θ : Pole의 각도
  • dx/dt : Cart의 속도
  • dθ/dt : θ의 각속도

게임이 끝나는 상황:

  1. θ가 15˚이상이 되었을 떄,
  2. 원점으로부터의 x의 거리가 2.4이상이 되었을 떄.

에이전트가 게임을 오래 지속하는 만큼 보상을 받고,
에이전트가 취할 수 있는 행동은 다음 두가지 입니다.

  1. 왼쪽으로 이동
  2. 오른쪽으로 이동
 

REINFORCE 에이전트

REINFORCE 에이전트 클래스를 만들어 줍니다.
REINFORCE에이전트는 0.001의 학습율($\alpha$), 0.99의 감가율($\gamma$)을 사용합니다.
REINFORCE 에이전트는 Policy Gradient를 인공신경망을 사용해 함수 근사를 한 에이전트 입니다.
인공신경망의 레이어 수는 세개, 각각 32개의 노드를 사용합니다.
Softmax정책을 사용하기 때문에 마지막 활성함수는 Softmax함수가 사용됩니다.
옵티마이저는 Adam을 사용합니다.
또한 에이전트는 현재 에피소드의 기록을 저장할 수 있는 메모리를 가지고 있습니다.

In [2]:
class REINFORCE:
    def __init__(self, num_states, num_actions):
        self.num_states = num_states
        self.num_actions = num_actions
        self.alpha = 0.001
        self.gamma = 0.99
        self.pi = nn.Sequential(
            nn.Linear(self.num_states, 32),
            nn.ReLU(),
            nn.Linear(32, 32),
            nn.ReLU(),
            nn.Linear(32, self.num_actions),
            nn.Softmax()
        )
        self.optimizer = optim.Adam(self.pi.parameters(), lr=self.alpha)
        self.memory = []
        ...
 

정책

REINFORCE는 정책을 인공 신경망을 통해 학습합니다.
따라서 정책신경망의 pi에서 Softmax함수에 의해 각 행동을 취할 확률이 나오게 되고,
그 확률을 torch의 Categorical 분포에 넣어줍니다.
그리고 해당 분포에서 샘플을 하게 되면 하나의 행동이 나오게 됩니다.
CartPole과 같은 두개의 행동이 존재하는 문제에서는 Categorical대신 Binomial을 사용해도 무관합니다.

In [2]:
class REINFORCE:
    ...
    
    def act(self, state):
        with torch.no_grad():
            state = torch.FloatTensor(state)
            policy_probs = torch.distributions.Categorical(self.pi(state))

        return policy_probs.sample()
        
 

메모리

REINFORCE는 몬테카를로 학습을 기반으로 하기 때문에, 에피소드의 경험을 저장할 필요가 있습니다.
에이전트가 환경을 통해 경험을 하면 각각의 경험을 메모리에 저장하게 됩니다.

In [2]:
class REINFORCE:
    ...
        
    def append_sample(self, state, action, reward):
        state = torch.FloatTensor(state)
        reward = torch.FloatTensor([reward])
        self.memory.append((state, action, reward))
    ...
 

반환값 계산

몬테카를로 학습은 감가율이 적용된 누적 보상(discounted cumulative reward)을 최대화 합니다.
따라서 에피소드의 마지막 step에서 멀리 떨어져 있을 수록 더 감쇄된 보상으로 인식해야 합니다.

In [2]:
class REINFORCE:
    ...
        
    def calculate_returns(self, rewards):
        returns = torch.zeros(rewards.shape)
        g_t = 0
        for t in reversed(range(0,len(rewards))):
            g_t = g_t * .99 + rewards[t].item()
            returns[t] = g_t
        return returns.detach()
        
    ... 
 

정책 네트워크 학습

감가율이 적용된 누적 보상을 통해 정책 네트워크가 학습됩니다.
이때 주의할 점은, loss가 마이너스 일 수 있다는 점입니다.
에이전트가 학습하는 방식은 경사상승법(gradient ascent)이기 때문입니다.
학습이 끝나면 다음 에피소드를 위해 메모리를 초기화 합니다.

In [2]:
class REINFORCE:
    ...
    def update(self):
        states = torch.stack([m[0] for m in self.memory])
        actions = torch.stack([m[1] for m in self.memory]) 
        rewards = torch.stack([m[2] for m in self.memory])
        
        returns = self.calculate_returns(rewards)
        returns = (returns - returns.mean()) / returns.std()
        self.optimizer.zero_grad()
        policy_log_probs = self.pi(torch.FloatTensor(states)).log()
        policy_loss = torch.cat([-lp[a].unsqueeze(0) * g for a, lp, g in zip(actions, policy_log_probs, returns)])
        policy_loss = policy_loss.sum()
        policy_loss.backward()
        
        self.optimizer.step()
            
        self.memory = []
        return policy_loss.item()
    ...
 

환경 & 에이전트 초기화

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

In [3]:
env = gym.make('CartPole-v1')
env = wrappers.Monitor(env, "./video", force=True)
observation = env.reset()
agent = REINFORCE(observation.shape[0], 2)

observation
Out[3]:
array([ 0.01359152, -0.03975019, -0.0469824 , -0.03837624])
 

에피소드 학습

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

In [4]:
rewards = []
for ep in range(500):
    done = False
    obs = env.reset()
    action = agent.act(obs)

    ep_rewards = 0
    while not done:
        next_obs, reward, done, info = env.step(action.item())
        ep_rewards += reward
        next_action = agent.act(next_obs)

        agent.append_sample(obs, action, reward)
        obs = next_obs
        action = next_action
    pi_loss = agent.update()
    rewards.append(ep_rewards)
    if (ep+1) % 10 == 0:
        print("episode: {}, loss: {:.3f}, rewards: {:.3f}".format(ep+1, pi_loss, ep_rewards))
env.close()
 
episode: 10, loss: -0.236, rewards: 49.000
episode: 20, loss: 0.608, rewards: 26.000
episode: 30, loss: 0.233, rewards: 22.000
episode: 40, loss: -0.135, rewards: 23.000
episode: 50, loss: -0.114, rewards: 24.000
episode: 60, loss: -0.310, rewards: 28.000
episode: 70, loss: -0.157, rewards: 12.000
episode: 80, loss: -0.061, rewards: 20.000
episode: 90, loss: 0.310, rewards: 37.000
episode: 100, loss: 0.268, rewards: 26.000
...
episode: 400, loss: -7.485, rewards: 115.000
episode: 410, loss: -3.342, rewards: 283.000
episode: 420, loss: -14.955, rewards: 500.000
episode: 430, loss: -3.324, rewards: 422.000
episode: 440, loss: -4.449, rewards: 500.000
episode: 450, loss: -5.099, rewards: 243.000
episode: 460, loss: 3.823, rewards: 250.000
episode: 470, loss: -3.393, rewards: 172.000
episode: 480, loss: -6.819, rewards: 232.000
episode: 490, loss: -1.050, rewards: 500.000
episode: 500, loss: -0.260, rewards: 413.000
 

에피소드 시각화

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

In [5]:
from utils import show_video

show_video()
 
 

학습 곡선 시각화

다음은 학습 시 받은 보상의 이동 평균(Moving Average)을 시각화 한 그래프 입니다.
비교 분석을 위해 이전 포스팅에 살펴본 딥러닝 기반 시간차학습 기법인 Deep SARSA가 함께 표기되어 있습니다.
CartPole은 행동공간이 적은 비교적 간단한 문제이기 때문에,
간단한 행동에 강한 REINFORCE가 Deep SARSA보다 뛰어난 것을 확인할 수 있습니다.

In [6]:
pd.Series(rewards).to_csv('./logs/rewards_reinforce_cartpole.csv')
In [8]:
deepsarsa_logs = pd.read_csv('./logs/rewards_deepsarsa.csv', index_col=False).iloc[:, 1]
reinforce_logs = pd.read_csv('./logs/rewards_reinforce_cartpole.csv', index_col=False).iloc[:, 1]
In [11]:
plt.figure(figsize=(16, 8))
plt.plot(reinforce_logs.cumsum() / (pd.Series(np.arange(reinforce_logs.shape[0]))+1), label="REINFORCE")
plt.plot(deepsarsa_logs.cumsum() / (pd.Series(np.arange(deepsarsa_logs.shape[0]))+1), label="DeepSARSA")
plt.legend()
Out[11]:
<matplotlib.legend.Legend at 0x7f06f9497f60>
 
In [ ]:
 

전체 코드

전체 코드는 다음 깃허브에서 확인할 수 있습니다.

728x90
반응형