2016년 7월 12일 화요일

Policy Gradients

직관적 이해

제어할 대상 시스템(환경, $env$), 시스템에 가하는 $action$, 이 $action$을 만들어 내는 제어기(DNN, CNN, or RNN)가 있다고 하자. 
요점은 $env$에 가해지는 최적 $action$ 샘플링 하나를 얻는 것이라기 보다, 랜덤 샘플링(가능한 여러 $action$ 중의 하나)을 제공하는 확률 분포(DNN 출력이 만드는)를 개선하는 것이다. 
$env$ 상태(state, 이미지 등) 입력에 대해 제어기(DNN)가 준 랜덤 $action$을 시스템($env$)에 가했을 때 받은 보상값($reward$)을 loss에 반영하여 DNN 학습을 반복하면, 이 샘플링을 발생 시켰던 분포(DNN 출력)가 $reward$를 크게하는 방향으로 개선된다.  



수식

신경망 $\pi$의 출력은 확률분포이며 기대값의 가중치로 사용된다. 아래 식 우측 [~] 부분은 loss의 grad이다.  




수렴성

Policy gradient는 여러 RL(강화 학습) 학습 방법 중에서 Alphago가 사용하였다. 확률분포가 $reward$를 크게하는 방향으로 수렴함을 확인해 보자.

용어들은 $^{각주1}$을 참고한다.


RL에서 학습이란 파라메터 값을 잘 바꾸어 분포를 조정함에 의해 $action$ 샘플링이 주는 보상함수를 높이는 것이 목표이다.
보상함수 $f(x)$ 기대치를 높이는 것이 목표이므로

$\nabla_\theta{E_x[f(x)]}$

처럼 기대치 $E$의 $\theta$에 대한 구배를 계산한다. 즉, 구배를 계산하여 갱신하면 $\theta$ 값을 바꿀 수 있다. $\theta$를 통해 $p$가 바뀌게 되고 여기서 샘플링되는 $action$이 바뀌게 된다. 위 식은 약간의 계산 절차를 통해

$E_x{[f(x){\nabla_\theta}\log{p(x)}]}$

가 되고, 이 값은 샘플링된 모든 $x$에 대해, 보상함수 $f(x)$와 ${\nabla_\theta}\log{p(x)}$의 곱에 대한 평균이다.




[from Karphaty's blog]

간단한 2차원 Gaussian $p(x)$로 위 수식에 따른 샘플들과 그 보상값이 분포를 어떻게 바꾸는지 확인해 보자.
$\log{p(x)}$에 대한 분포 파라메터(여기서는 평균값 $m$, 따라서 $p(x;\ m)$이다)에 대해 도함수를 계산하면

${\nabla_\theta}\log{p(x)}$ = $c_1(x-m)$

이다. 즉, 평균점 $m$에서 샘플 점 $x$를 향하는 벡터가 된다. 첫번째 그림에서 파란색 점은 샘플점들을 나타내고 화살표는 그 방향을 보여준다.

두번째 그림에서 샘플들 위치에서 얻어진 보상함수 값을 표현한다. 특정 샘플은 +1의 보상치(녹색)를 가지고 나머지는 -1 보상치(주황색)를 가진다.
보상치와 벡터들을 곱하고 평균을 내면 분포 파라메터(여기서는 mean 위치 $m$)가 움직여야 하는 방향이 계산되고, 왼쪽 아래 방향이 나오게 된다.

계산된 방향에 따라 분포(평균 위치)를 조정하면 세번째 그림이 되고, 이제 새로운 분포에서 샘플링된 점들은 보상치가 +1이 될 가능성이 더욱 높아지게 된다.



실제 적용 예OpenAI의 pong게임을 이용한 것으로 초기에는 컴퓨터가 주로 이기나, 학습이 진행될수록 agent가 이기는 확률이 높아진다.




(각주 1)

$\bullet$ $p(x;\theta)$: 입력 $x$와 결합된 파라메터 $\theta$의 조합으로 값 $p$가 결정된다는 의미이고 $p$는 확률 분포(비슷한 내용으로 여기 참고)이다.  즉, $p$는 agent action을 만들어 내는 policy를 나타낸다. agent가 선택하는 action은 확률분포 $p$에서 샘플링을 통해서 생성된다.

예를 들어 $p$가 어떤 입력에 대해 action분포를 만들어 내는 CNN으로 구성된 policy network이라면, 이미지 $I$가 입력 되었을 때 action에 대한 분포인 $p(a|I;\theta)$가 되고, $\theta$는 넷 내부 weights 등 파라메터가 된다.

$p(x)$는 $p(a)$이고, 분포 $p(a)$를 바꾸려면 이미지 입력에 의해 만들어지는 $p(a)$에 관여하는 파라메터 $\theta$를 바꾸어야 한다.


$\bullet$ $f(x)$: 함수 $f$ 인자인 $x$는 $p$상에서 sampling되며 선택된 샘플(action)이 만들어 내는 보상 함수(scalar값을 가짐)이다. 선택된 action으로 끝까지 게임을 진행했을 때 win, fail에 대한 보상치이다.





References
[1] Mastering the game of Go with deep neural networks and tree search, Nature, 2016.
[2] http://karpathy.github.io/2016/05/31/rl/
[3] 한정수, 정책기울기 값 강화학습을 이용한 적응적 QoS라우팅 기법연구, 컴퓨터정보학회, 2011.

[4] What's right way of implementing policy gradient?





댓글 5개:

  1. # ENAS-pytorch에서 사례

    hidden = self.shared.init_hidden(self.args.batch_size)

    for step in range(self.args.controller_max_step):
    {

    # sample models (다양한 rnn구조를 샘플링). dags는 복수의 구조들의 리스트.
    dags, log_probs, entropies = self.controller.sample(with_details=True)

    # reward 계산 (각 구조가 만든 net의 분류 성능이 얼마나 좋은지 체크)
    rewards, hidden = get_reward(dags,np_entropies, hidden, valid_idx)

    # discount (최신 rewards만 취함)
    if 1 > self.args.discount > 0:
    rewards = discount(rewards, self.args.discount)

    # moving average baseline (강화학습 중 REINFORCE 방법일 때)
    if baseline is None:
    baseline = rewards
    else:
    decay = self.args.ema_baseline_decay
    baseline = decay * baseline + (1 - decay) * rewards

    adv = rewards - baseline # 실제 이 값이 보상치가 됨


    # policy loss (log 확률과 보상치를 곱한 값)
    loss = -log_probs*utils.get_variable(adv, self.cuda, requires_grad=False)

    loss = loss.sum() # or loss.mean()

    # update
    self.controller_optim.zero_grad()
    loss.backward() # loss의 미분이니 PG의 update 식이 된다.

    }

    답글삭제
  2. #
    # pytorch에서 Categorical 사용법:
    # pytorch의 Categorical은 Multinomial과 같다. prob나 log_prob(logits)를
    # 넘겨주면 다항분포 index를 리턴(즉, 높은 확률값의 index리턴)
    # prob=[m,n]이면 m.sample()은 m-vector 리턴
    #
    >x = torch.Tensor([ [0.25, 0.5, 0.25], [0.7, 0.2, 0.1]])
    >y = torch.autograd.Variable(x, requires_grad=True)
    >m=torch.distributions.Categorical(y)
    >
    > for i in range(10):
    ... action = m.sample()
    ... print (action, m.log_prob(action))
    ...
    tensor([ 0, 0]) tensor([-1.3863, -0.3567])
    tensor([ 1, 0]) tensor([-0.6931, -0.3567])
    tensor([ 1, 2]) tensor([-0.6931, -2.3026])
    tensor([ 2, 2]) tensor([-1.3863, -2.3026])
    tensor([ 1, 1]) tensor([-0.6931, -1.6094])
    tensor([ 1, 0]) tensor([-0.6931, -0.3567])
    tensor([ 2, 0]) tensor([-1.3863, -0.3567])
    tensor([ 0, 0]) tensor([-1.3863, -0.3567])
    tensor([ 2, 0]) tensor([-1.3863, -0.3567])
    tensor([ 0, 0]) tensor([-1.3863, -0.3567])
    >
    > math.log(0.25) # -1.3862943611198906
    > math.log(0.5) # -0.6931471805599453
    > math.log(0.2) # -1.6094379124341003
    > math.log(0.7) # -0.35667494393873245
    >


    답글삭제
    답글
    1. #
      # Test example:
      #
      >import torch, math
      >
      >x = torch.autograd.Variable( torch.Tensor([0.1, 0.9]), requires_grad=True )
      >
      >m=torch.distributions.Categorical(x)
      >action = m.sample() # 1
      >
      >for i in range(10):
      ... action = m.sample()
      ... print(action, m.log_prob(action))
      ...
      tensor(1) tensor(-0.1054) #1에서 평가된 pdf의 log값
      tensor(1) tensor(-0.1054) # -> math.log(0.9)
      tensor(1) tensor(-0.1054)
      tensor(1) tensor(-0.1054)
      tensor(1) tensor(-0.1054)
      tensor(0) tensor(-2.3026) #0에서 평가된 pdf의 log값
      tensor(1) tensor(-0.1054) # -> math.log(0.1)
      tensor(1) tensor(-0.1054)
      tensor(1) tensor(-0.1054)
      tensor(1) tensor(-0.1054)

      삭제
  3. # pytorch에서 사례
    >
    >probs = policy_network(state)
    >
    # NOTE: categorical is equivalent to what used to be called multinomial
    >
    >m=torch.distributions.Categorical(probs)
    >
    >action = m.sample()
    >
    >next_state, reward = env.step(action)
    >
    >loss = -m.log_prob(action) * reward
    >
    >loss.backward()
    >

    답글삭제
  4. # 블로그 사례
    # https://discuss.pytorch.org/t/whats-the-right-way-of-implementing-policy-gradient/4003
    >
    >loss = - torch.sum(torch.log( policy(state) * (reward - baseline)))
    >
    then you compute the gradient of this loss with respect to all the parameters/variables that requires a gradient in your code by calling:
    >
    >loss.backward()
    >
    and if you created, before the training loop, an optimizer associated to your policy like this:
    >
    >optim_policy = optim.MyOptimizer( policy.parameters(), lr = wathever)
    >
    you can update your optimizer simply like this, after each backward call of your loss:
    >
    >optim_policy.step()
    >
    the parameters of your policy (theta) will be updated with the gradient of your loss in order to minimize your loss.

    답글삭제