강화학습/RL 강의 정리

모두를 위한 RL강좌 정리하기(Lecture 3 ~ Lab 3)

APinCan 2020. 8. 11. 23:56

이번에는 Q러닝에 대해서 배운다.

에이전트가 목표를 찾아가는데 있어서 에이전트는 환경을 모르기 때문에 목표까지 어떻게 찾아갈 것인지를 정해야한다. 일단 첫번째로 에이전트는 랜덤하게 이동하면서 목표를 찾아갈 수 있다. 근데 다만, 이것보다는 이제부터 목표를 찾아가는 새로운 기준이 생기는데 그게 바로 Q다.

Q는 에이전트에게 현재 상태에서 어디로 가야할지를 알려주는 것이라고 할 수 있다. 에이전트는 환경에 대해서는  잘 모르지만 Q를 알고 이 정보를 가지고 어디로 갈지를 결정하게 된다.

Q함수는 다음과 같이 이루어져 있다. 즉 어떤 상태(state)와 행동(action)을 인풋으로 주면 그에 맞는 Q값이 리턴되는 것이다. 이런 함수같은 구조 때문에 Q함수라고도 부른다. 큐함수를 아는 에이전트는 그러면 이를 어떻게 이용할까?

에이전트는 큐함수 중 가장 큰 값을 따라간다. 예를 들어 그림을 보면 일단 첫번째로 어떤 state s1에서 가장 큰 큐값을 찾는다. 그리고 그 찾은 값의 argument를 구해 어떤 action인지 찾는 것이다. 이 예제에서는 가장 큰 큐값이 0.5이고 그 action은 바로 RIGHT임을 알 수 있다.

이를 다음과 같이 표현할 수 있다. 어떤 state s에서 action a' 중 가장 큰 값은 Max Q이고, 그 Max Q값의 아규먼트인 action a를 찾아가는게 optimal policy이다. 즉 에이전트가 어떤 상태에서 목표를 찾아갈 때, 매번 Q함수가 가장 높은 action을 찾아가는 것이 optimal policy라고 할 수 있다.

그러면 이제 이 Q를 찾아야 한다. 그러기 위해서 어떤 가정을 할 것인데 현재 상태의 다음 상태인 s'에도 Q가 있다고 가정한다. 그러면 에이전트가 있는 현재 상태 s에서 어떤 action a를 한다면 에이전트는 다음 상태인 s'으로 갈 것이다. 이 때 받는 보상(reward)는 r이라고 한다. 자 다음상태의 큐함수 Q(s',a')로 현재 상태에서의 큐함수 Q(s,a)를 표현해보자.

일단 에이전트가 종점에 갈때까지 거쳐간 state, action와 받은 reward들의 순서는 위와 같이 표현할 수 있다. 그리고 에이전트는 optimal policy를 따라서 action을 선택한다.  이를 해석하면 '현재 상태 s에서 선택한 action a의 큐 = 다음 상태 s'으로 갈 때 받을 보상(r) + 다음 상태 s'에서 가장 높은 큐값을 가진 action a'의 큐값'이라고 얘기할 수 있다.

이제 이걸 Frozen Lake 예제를 이용해서 실제로 큐값을 어떻게 찾나 알아보자. Frozen Lake의 테이블 내에 모든 큐값은 일단 0으로 초기화된다. 시작지점 s에서 에이전트가 출발을 하는데 일단 처음에는 reward도 0이고 다음 상태의 큐값도 0이기 때문에 현재 상태의 큐값을 0 이외의 다른 값으로 업데이트 할 수가 없다. 다만 여기서 골인지점인 초록색까지 에이전트가 가게 된다면 다음 상태인 초록색 지점의 큐값은 0이지만 reward로 1을 받게 되 현재 에서 오른쪽으로 갈때의 큐값이 업데이트된다.

이를 식으로 표현하면 다음과 같다. 이런 계산을 통해서 state s13(종점에서 한단계 전)에서 오른쪽으로 갈때의 큐값은 1이 된다. 이렇게 하나의 에피소드마다 차근차근 큐값을 업데이트한다.

그러면 예를 들어 다음과 같이 큐값이 업데이트 된다. 그리고 이게 바로 큐값이 항상 높은 것을 따라가는 optimal policy이다.

이를 알고리즘으로 표현하면 다음과 같다. 각 state s, action a마다 테이블의 모든 큐값을 0으로 초기화 한다. 그리고 현재 상태 s에서 action a를 선택한다. 그리고 reward r을 받고 새로운 state s'을 확인한다. 이제 현재 상태 s에서 action a의 큐값을 업데이트한다. 업데이트 식은 위와 같다. 업데이트 한 후에는 다음 상태 s'으로 이동 한다.

이를 계속 반복하는 것이 큐러닝 알고리즘이다.



이제 다음으로 코드를 짜보자. 환경은 앞에서 계속하던 FrozenLake이다.

왼쪽과 같은 알고리즘을 오른쪽같이 코드로 그대로 옮긴다.

동일하게 임포트 해주고(다만 pr보다 random이 더 익숙해서 이쪽으로 했다)


이 코드는 레퍼런스가 따로 있다(https://gist.github.com/stober/1943451)

하는 일을 보면 일단 넘어 온 vector(Q값들) 중에서 가장 큰 값을 m에 저장하고 np.nonzero를 사용해 vector==m, 즉 가장 큰 값들을 가지는 인덱스들을 반환한다. 그리고 [0]를 넣어서 리스트만을 반환하는데 그 값을 indices에 저장한다. 그리고 그 중에 랜덤하게 하나의 값을 뽑아서 리턴 시켜주는 것이다.

요약해서 말하자면 가장 큰 Q 값들이 중복될 수 있으니 그 중 랜덤으로 하나만 선택하는 코드이다.


그다음에는 FrozenLake의 환경설정을 해주는 코드다.

환경설정을 해주고 Q값을 저장하기 위한 배열을 생성하고 0으로 초기화한다. 이 예제에서는 observation_space.n, 즉 state의 개수는 16개가 된다. action_space.n은 상,하,좌,우 4개가 되므로 16,4의 배열을 생성하고 0으로 초기화한다.

그리고 총 에피소드의 개수는 2000개로 제한한다. 에이전트가 FrozenLake를 탐험하다 구멍에 빠지던 목표지점에 도착하던지 하면 하나의 에피소드가 끝나는데 이걸 2000번 하겠다는 얘기가 된다.


알고리즘과 거의 똑같이 구현한 에이전트의 핵심부분이다.

일단 우리는 에피소드를 진행하며 얻는 reward를 확인하기 위해  rList를 생성한다. 그리고 총 2000번의 에피소드를 진행한다고 했으니 2000번의 루프를 돌게 한다. 각 에피소드마다 env.reset()을 통해 환경을 초기화 시킨다.

일단 done=False로 설정해서 while문에 진입하고 에피소드가 끝나는 조건(ex. 구멍에 빠지거나, 목표지점에 도착하거나)이 만족되면 done=True를 리턴에 while문이 끝날 것이다.

while문에서는 rargmax를 통해 각 state에 맞는 큐함수들을 전달받는다. 거기서 action의 인덱스를 리턴받고 env.step(action)을 통해 다음 state와 reward, 그리고 done인지 알게된다. 알게된 값을 통해 큐값을 업데이트한다. 그리고 에피소드에서 얻은 모든 reward를 rAll에 더하고 에이전트를 새로운 state로 이동시킨다.

이렇게 모든 2000번의 에피소드로 큐값을 업데이트한다.


Success rate는 rList 내에 있는 1들의 개수의 합 / 에피소드의 개수로 에이전트가 2000번의 에피소드에서 목표지점을 제대로 찾아간 경우를 나타낸다. 에이전트는 초반에는 랜덤하게 이동하기 때문에 이 확률은 실행시킬 때마다 달라질 것이다.

그 다음은 각 state마다 Q값들을 보여주는 것인데 이거도 실행할 때마다 조금씩 달라질 수 있다.


마지막으로 이걸 그래프로 표현한건데 대충 에피소드 100번? 정도부터 제대로 된 optimal policy를 따라서 그 길로만 간 것을 확인할 수 있다. 



Reference:

[1]: http://hunkim.github.io/ml/

[2]: https://youtu.be/Vd-gmo-qO5E

[3]: https://youtu.be/yOBKtGU6CG0