기계학습개론 과제 - 오델로 에이전트 학습시켜보기

이번 학기에 수강하고 있는 기계학습개론의 과제가 교수님이 내주시는 과제를 하거나, 알아서 하고 싶었던 머신러닝 과제를 잘 해오는 것이기 때문에, 나는 예전부터 해보고 싶었던, 게임 에이전트 만들어보기를 진행하기로 했다. 그래서 이 포스트는 그 과제를 진행하는 과정과 그 결과를 간략하게 정리해보려고 한다. 나중에 보고서 쓸 때 편할려고 하는 것도 있다.

목차

길어질 거 같아서 써봤다. 하위 주제는 안씀

주제 정하기

1년 반즈음 전에 내가 오델로를 좋아했기 때문에 오델로로 머신러닝 에이전트를 작성해보아야지하고 작성을 해본적이 있었다. 물론 제대로 작성을 못하고 필요한 지식에 비해 내가 가진 정보는 적었기 때문에 고치지도 못했다. (내가 적당히 만들었던 에이전트에게는 승률 30%정도로 끝까지 못 이겼다.) 그 때는 머신러닝 모델의 아키텍쳐에 대해 제대로 몰라서, 오델로는 8x8의 판을 사용하니까 64개의 output node를 가진 FC를 여러개 이어붙이면 되겠지? 하고 작성을 했었다. 물론 동작은 했는데, 승률은 처참했다.

2017년에 작성하던 머신러닝 기반 에이전트

그래서 이 주제로 제대로 작성해보기로 했다. 한학기동안 머신러닝 수업을 들은 것도 있고, 그 동안 컨퍼런스에서 관련 세션을 여러번 들은 것도 있고 해서 한번 해볼만 하지 않을까??싶었다. 무엇보다 교수님이 실패를 하여도 실패한 것에 대해 제대로 써오면 괜찮다고 하셨다.

근데 사실 word2vec 해보고 싶었고 열심히 공부하다가 “내가 이거 공부한다고 의미가 있을까?”라는 생각을 하다가 이걸로 바꿨다. 근데 그거나 그거나인거 같다

어떤 것으로 구현해볼까

예전에도 파이썬을 사용했고, 요즈음에도 파이썬을 자주 사용하므로, 의심의 여지 없이 언어는 파이썬으로 진행한다. 그리고 강화학습 방식을 채택한다. 많은 데이터를 모으기 힘들기 때문에 강화학습을 통해 학습을 시키려한다.

이론

공부하던 거를 여기 정리한다.

오델로

모르는 사람은 없을 거라 생각하지만, 구현하면서 생각해야할 것들을 정리하자.

  • 판의 크기는 8x8
  • 두 사람이 돌아가면서 놓음
  • 턴수는 일정하지 않음
    • 마지막에 상대가 둘 돌이 없으면 (감쌀 돌이 없으면) 내가 둘 수도 있음

강화학습

이제는 너무 유명해져서 다들 잘 알겠지만, Agent가 State을 보고 Action을 취한 후 State가 변하여 Reward를 획득한다. 그냥, Agent가 State S(n)에 대해 Action A(n)을 취하고 RewardR(n)을 받고 S(n+1)을 보고, 그 S(n+1)에 대해 A(n+1)을 취하고.. 그런거다. 그 보상을 최대화 시키는 방향으로 간다.

MDP

강화학습이 순차적으로 행동을 결정하는 문제를 푸는 것이기 때문에 정의한 것이 MDP라고 한다. MDP는 Markov Decision Process이다. MDP는 (상태, 행동, 보상함수, 상태 변환 확률, 감가율)로 구성되어 있다. 다른 것들은 다 아는 가고, 상태변환확률이 헷갈려서 찾아보았는데, State Transition Probability라고 한다. 감가율은 참고로 Discount Factor. 상태 변환 확률은 Agent가 취하는 Action에는 확률적인 요인이 들어있기 때문에 수치적으로 넣은 것이 상태 변환 확률이라고 한다. (오델로에는 그닥 필요없는 조건인 것 같다.) 감가율은 나중에 얻은 보상이 먼저 얻은 보상보다 안좋아서 나중에 얻은 보상을 덜 좋게 만들어주는 건데, 처럼 특정한 인자를 곱해준다. 근데 오델로는 똑같은 수를 두니까 별 필요가 없을 것 같은데, 대신 보상을 양 측의 점수차로 두어야 잘 될 것 같다는 생각은 한다.

정책

강화학습에서 정책이 필요한데, 그 이유는 Agent가 어떤 룰로 State에 대한 Action을 정하는 지가 필요하기 때문이다. 음.. 나중에 기억나면 여기로 다시 돌아와서 적음.

벨만 방정식 (Bellman Equation)

Value Function이 정책에 대해 영향을 받아서 정책을 반영할 경우 Bellman Equation이 된다고 한다.

이 식은 Bellman Expectation Equation이다.

구현해보자

미리 정할 것

State

State는 8x8의 matrix로 두기로 하고, 각각의 값은 내 돌을 1, 상대의 돌을 -1로 두자.

가능한 Action

Action Space를 따지면 64개가 가능하다. 하지만 때에 따라 둘 수 있는 범위가 다르긴 한데, 그냥 그대로 두고, 학습을 잘 시켜서 둘 수 있는 곳안에서만 고르도록 하자. 그래서 원하는 방향은 예전이랑 똑같이 64개의 결과값이 나왔으면 한다.

오델로 구현하기

이건 정말 간단하게 구현했다. 에이전트 두개를 등록하면 각각 돌아가면서 둔다. Agent의 Action은 아래처럼 정의했다.

class Action:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

그냥 간단하게 x, y를 저장하는데 대신 validating 할 때 0~7인지만 검사한다. 오델로 게임은 한 80라인 되어서 그대로 붙여넣기 하기에 좀 그래서 그냥 핵심만 여기 적는다.

class Othello:
    ...

    def agent_actor(self, func):
        """register function as agent actor
        """
        if len(self.agents) is 2:
            raise Exception("You cannot register 3 or more agents.")

        self.agents.append(func)
        return func

    def renderer(self, func):
        """regiseter function as renderer
        """
        self.renderers.append(func)
        return func

    ...

위처럼 decorator를 작성해서 등록하도록 했다. 대신 agent는 두개만 등록가능하도록 했다.

class Othello:
    ...

    def play(self):
        """play othello
        """
        if len(self.agents) is not 2:
            raise Exception("You have to register 2 agents to play Othello.")

        turn = 0
        invalid_before = False

        while not self.is_end_of_game:
            action, is_pass = self.agents[turn](self.board, turn,
                                                invalid_before)

            if is_pass:
                pass
            elif self._is_valid_action(turn, action):
                self._act(turn, action)

                for rd in self.renderers:
                    rd(action, turn)
            else:
                invalid_before = True
                continue

            # toggle turn
            turn = int(not turn)
            invalid_before = False
            self._check_end_of_game()

이렇게 그냥 계속해서 플레이를 한다. 패스일 경우에는 그냥 패스. 렌더러가 있으면 새로운 액션이 발생할때마다 렌더링 해준다.

import numpy as np

from PIL import Image, ImageTk

from .othello import Othello


class Visualizer:
    def __init__(self, othello, path='./result'):
        if not isinstance(othello, Othello):
            raise Exception("Object othello must be an instance of Othello.")
        self.othello = othello
        self.othello.renderer(self.render)
        self.count = 0
        self.path = path

        self.image = np.zeros((401, 401))
        self.image[:, :] = 128

        for i in range(9):
            k = i * 50
            self.image[k, ] = self.image[:, k] = 255

    def render(self, action, turn):
        self.image[action.y * 50 + 1:action.y * 50 + 50, action.x * 50 +
                   1:action.x * 50 + 50] = turn * 255
        image = Image.fromarray(self.image.astype('uint8'), 'L')
        image.save(f"{self.path}/task{self.count}.png")
        self.count += 1

Visualizer라고 하긴 그런데, 매 액션을 이미지로 저장하도록 했다. 처음에는 tkinter 쓰려고 했는데, mainloop때문에 안썼다.

이렇게 적당히 Othello를 구현했다. 그럼 그 다음은 Agent를 작성할 차례.

Agent 구현하기

정확한 구현법은 모르겠지만, reward에 따라서 epoch를 다르게 했다.

근데 plaidml-keras에서 load_weights가 동작을 안한다.. 기껏 학습을 시켜보고 로드도 해보았는데, 커피를 마시고 돌아와도 멈추어있길래 이상해서 Google Colab으로 학습시켜보니 잘 돌아간다. 그래서 .h5파일만 받아온 다음에 로컬에서 돌려보았다.

마무리

이렇게 열심히 썼는데 사실 잘 안돌아간다. 내가 생각했던 대로 안돌아가서 일단 이건 일단 여기까지만 작성을 한다.. ㅠㅠ 학습을 시키는 과정에서 문제가 있었던 것 같다. 그리고 위의 코드는 좀 많이 고쳐졌다. (이것저것 고쳐보면서..ㅠㅠ) 특히 오델로 플레이 하는 부분에서 둘다 아무곳에도 둘 수 없는 경우가 존재하는 예외처리 등등을 추가로 작성했었다.

일단 다음에 다시 만들어볼 생각이 있으므로 아래에 메모..

  1. 놓을 수 있는 지점을 먼저 학습시켜서 이용하자.
  2. 내가 지금 구현한 모델은 에이전트에서 각 확률을 뽑은다음에 argsort를 시켜서 놓을 수 있는 가장 높은 확률의 칸에 두는 것인데, 음.. 이렇게는 하지말자
  3. 출력값을 8*8로 그대로 뽑은 것은 잘한것 아닐까?

참고

December 1, 2018 에 작성
Tags: machine learning python 대학