본문 바로가기

트렌드 한눈에 보기/학계 트렌드

PyTorch를 활용한 Actor-Critic Tutorial 2탄

Actor Critic의 역사가 생각보다 오래되어서

내가 적용한 알고리즘에 대체 어느 논문에 기반해있는지 알아보기가 어려웠다.

OpenAI의 한 연구원이 정리해놓은 내용에 따르면

2016년부터 현재까지 활발히 사용되는 알고리즘만 7개는 넘는 것 같다.

AC가 들어갔으면 모두 Actor Critic 기반일 테다

 

Actor Critic 자체는 1998년 머신러닝 교과서에도 수록되어있는 터라,

PyTorch로 구현한 내용은 이와는 분명히 다를 텐데... 하며 검색하다가

2016년에 발표된 논문을 찾을 수 있었다.

 

하지만 생각보다 논문이 너무 무섭게(어렵게) 쓰여있는 바람에

읽어볼 엄두는 못내고, 일단 코드를 분석하면서 의미를 파악해보기로 했다.

기본적인 Actor Critic의 내용은 지난 CS234 수업에서 배웠듯이,

Policy와 Value 네트워크를 따로따로 학습시키는 것을 뜻한다.

이 정도 배경지식으로 일단 코드에 부딪쳐도 될 것이다.

 

SavedAction = namedtuple('SavedAction', ['log_prob', 'value'])
# action 모음집: 왜 log_probability와 value를 함께 뒀을까?

class Policy(nn.Module):
# Policy 네트워크: Fully Connected Layer들로 구성
# 
  def __init__(self):
    super(Policy, self).__init__()
    self.affine1 = nn.Linear(4, 128)
    
    # actor layer
    self.action_head = nn.Linear(128,2)

    # critic layer
    self.value_head = nn.Linear(128,1)

    # action & reward buffer
    self.saved_actions = []
    self.rewards = []

  def forward(self, x):
    x = F.relu(self.affine1(x))

    action_prob = F.softmax(self.action_head(x), dim = -1)

    state_values = self.value_head(x)

    return action_prob, state_values

model = Policy()
optimizer = optim.Adam(model.parameters(), lr = 3e-2)
eps = np.finfo(np.float32).eps.item()

 

코드에서는 Policy라는 클래스에 Actor(policy network)와 Critic(value network)이 공존한다.

원래 action을 모아둔 것이 Policy Network라고 생각했기에 

Policy 클래스와 Value 클래스가 따로 존재할 거라고 생각했는데 오산이었다.

거 봐, 이렇게 다르다니까.

 

forward 함수를 보면, 입력 x에 대해 action head를 거치면 action probability가 나오고

value head를 거치면 state value가 나오게끔 학습이 되는 듯 하다.

둘의 역할은 아직 잘 모르겠다.

 

def select_action(state):
  state = torch.from_numpy(state).float()
  probs, state_value = model(state)

  m = Categorical(probs)

  action = m.sample()

  model.saved_actions.append(SavedAction(m.log_prob(action), state_value))

  return action.item()

def finish_episode():
  R = 0
  saved_actions = model.saved_actions
  policy_losses = []
  value_losses = []
  returns = []

  for r in model.rewards[::-1]:
    R = r + 0.99*R
    returns.insert(0, R)

  returns = torch.tensor(returns)
  returns = (returns - returns.mean()) / (returns.std() + eps)

  for (log_prob, value), R in zip(saved_actions, returns):
    advantage = R - value.item()

    policy_losses.append(-log_prob * advantage)

    value_losses.append(F.smooth_l1_loss(value, torch.tensor([R])))

  optimizer.zero_grad()

  loss = torch.stack(policy_losses).sum() + torch.stack(value_losses).sum()

  loss.backward()
  optimizer.step()

  del model.rewards[:]
  del model.saved_actions[:]

 

Select action과 finish episode, 두 함수로 필요한 준비는 끝난다.

확실히 DQN보다 코드가 간결한 느낌이 든다.

Select action 함수는 DQN에서 Replay Memory를 사용했듯이,

행동 내역들을 차곡차곡 저장해주기도 하고, 추출해주기도 하는 역할을 한다.

Finish episode 함수는 DQN의 Optimize 함수와도 같은 역할로서

보상을 누적시키고, 학습을 진행시켜준다.

출처: Lilian Weng 블로그 (OpenAI 연구원)

Finish episode가 본 알고리즘의 핵심이라고 할 수 있는데, 

위 알고리즘 pseudo code에 담긴 내용이 모두 반영되어 있다.

policy 파라미터들이 임의로 추출된 보상, 상태, 행동값에 따라 업데이트 되고,

advantage(취했던 행동이 얼마나 좋은 선택이었는지 평가하는 지표)를 통해

value 파라미터들도 업데이트해 줄 수 있는 것이다.

 

말로만 풀어써보니, 정말 되긴 하는 건가 싶다.

DQN을 공부할 때는, "아하!" 하고 이마를 탁 치는 맛이 있었는데

의구심만 가득해지는 기분이다.

 

내일은 조금 오래 걸리더라도 논문을 한 번 읽어보는게 좋겠다.

긴 하루가 되겠군....