Sunday, October 15, 2017

슈퍼마리오에 모두를 위한 RL 수업의 딥러닝 코드 붙이기


윈도우에서 openAI Gym을 이용해서 슈퍼마리오 AI 플레이 만들기


1. 윈도우 서브시스템 리눅스에 openAI Gym 설치하기
http://jinman190.blogspot.ca/2017/10/openai-gym.html

2. openAI gym을 이용해서 슈퍼마리오 설치해서 자동 플레이 하기
http://jinman190.blogspot.ca/2017/10/openai-gym_14.html

3. 슈퍼마리오에 모두를 위한 RL 수업의 딥러닝 코드 붙이기
지금 여기

4. 슈퍼마리오의 딥러닝 코드 효율적으로 고쳐보기






목표: 모두를 위한 RL 수업에서 쓰인 코드로 슈퍼마리오를 실행 해 보려고 한다.
(효율적인 딥러닝을 하는게 아니라 우선 변수 차원 맞춰서 에러없이 실행만 해보기)



Sung Kim 교수님의 모두를 위한 RL 수업은 여기.
https://www.youtube.com/playlist?list=PLlMkM4tgfjnKsCWav-Z2F-MMFRx-2gMGG

코드는 여기서 받았다.
https://github.com/nalsil/kimhun_rl_windows/blob/master/07_3_dqn_2015_cartpole.py




알아내느라 꽤 걸렸는데 실제로 고친건 몇줄 안된다.


최종 코드 파일은 여기:
https://docs.google.com/document/d/16LLmgRPGZF2-wEa57uSbvwhDlnaPgyZLA9OnyImR2mQ/edit?usp=sharing

내가 고친 부분엔 ##### 표시를 해뒀다.





원래 소스는 Cartpole 게임을 플레이 하는 소스이다.


우선 받은 소스 위에 앞 글에서 찾아낸 슈퍼마리오 관련 코드를 붙이고

import gym
from gym.envs.registration import register
from gym.scoreboard.registration import add_group
from gym.scoreboard.registration import add_task
register(
     id='SuperMarioBros-1-1-v0',
     entry_point='gym.envs.ppaquette_gym_super_mario:MetaSuperMarioBrosEnv',
)

add_group(
     id='ppaquette_gym_super_mario',
     name='ppaquette_gym_super_mario',
     description='super_mario'
)

add_task(
    id='SuperMarioBros-1-1-v0',
    group='ppaquette_gym_super_mario',
    summary="SuperMarioBros-1-1-v0"
)

게임 이름을 슈퍼마리오로 고쳐준다

env = gym.make('SuperMarioBros-1-1-v0')

다음으로 인풋/아웃풋 개수를 맞춰준다

input_size = env.observation_space.shape[0]*env.observation_space.shape[1]*3
output_size = 6

처음에 인풋 갯수가 172032개여서 헉 이게 뭐지! 했는데
스크린 픽셀 개수 가로*세로*3 이다.
ppaquette_gym_super_mario/nes_env.py 에서 정보를 찾을 수 있다.



스크린 픽셀 갯수에 *3이면 RGB 값... 그렇다면...


갑자기 이런게 막 떠오르려고 하는데 우선 넘어가자








소스의 아랫 부분을 보면 원래 이런 부분이 있는데 고쳐줘야 한다.
            while not done:
                if np.random.rand(1) < e:
                    action = env.action_space.sample()
                else:
                    # Choose an action by greedily from the Q-network
                    action = np.argmax(mainDQN.predict(state))


랜덤으로 입력을 받는 부분인 env.action_space.sample() 은 잘 돌아가는데 아래 action 부분이 원래 cartpole 게임에 맞춰져 있다. 이렇게 바꿔야 한다.

action = mainDQN.predict(state).flatten().tolist()
숫자 6개 출력을 받아서 argmax 하지말고 그냥 리스트로 만들어서 주면 된다.





드디어 슈퍼마리오를 플레이 해보면, 슈퍼마리오가 한번만 실행되고 에러가 난다.




ddqn_replay_train()에

            y_stack = np.vstack([y_stack, Q])
            x_stack = np.vstack([x_stack, state])
이 부분의 state가 한줄(array) 형태가 아니고 matrix 여서 vstack을 쓸 수 없다.
state를 한줄로 쫙쫙 펴줘야 한다.

            y_stack = np.vstack([y_stack, Q])
            x_stack = np.vstack([x_stack, state.reshape(-1, mainDQN.input_size)])
그래야 vstack 함수 그대로 쓸 수 있다.






여기까지만 고쳐도 실행해보면 슈퍼마리오가 돌아간다. 그런데 꼭 고쳐줘야 하는 부분이 있다.



위에서 아웃풋 크기가 6라고 했는데 무슨 뜻인지는
ppaquette_gym_super_mario/wrappers/action_space.py 에서 찾을 수 있다.

                0: [0, 0, 0, 0, 0, 0],  # NOOP
                1: [1, 0, 0, 0, 0, 0],  # Up
                2: [0, 0, 1, 0, 0, 0],  # Down
                3: [0, 1, 0, 0, 0, 0],  # Left
                4: [0, 1, 0, 0, 1, 0],  # Left + A
                5: [0, 1, 0, 0, 0, 1],  # Left + B
                6: [0, 1, 0, 0, 1, 1],  # Left + A + B
                7: [0, 0, 0, 1, 0, 0],  # Right
                8: [0, 0, 0, 1, 1, 0],  # Right + A
                9: [0, 0, 0, 1, 0, 1],  # Right + B
                10: [0, 0, 0, 1, 1, 1],  # Right + A + B
                11: [0, 0, 0, 0, 1, 0],  # A
                12: [0, 0, 0, 0, 0, 1],  # B
                13: [0, 0, 0, 0, 1, 1],  # A + B

그냥 float 6개를 넘기면 되는게 아니라 0 또는 1로 넘겨줘야 하는 것이다.

참고로 이거, float 0.0이나 1.0으로 넘겨도 안된다. 꼭 integer 0이나 1이어야 한다.

이 외의 숫자를 넘기면 슈퍼마리오가 아예 움직이지 않는다.

위에서 그냥 슈퍼마리오를 실행 했을 때 슈퍼마리오가 움직이는건 내가 계산해 낸 출력 때문이 아니라 윗줄의 랜덤 출력

                if np.random.rand(1) < e:
                    action = env.action_space.sample()
때문이다.




결과값이 0, 1이라니... 여기에 또 뭘 적용해볼까 막 시그모이... 같은게 떠오르지만, 

우선 슈퍼마리오를 실행하는게 목표니까 그냥 이렇게 하자.


                    for i in range(len(action)):
                        if action[i] > 0.5 :
                            action[i] = 1
                        else:
                            action[i] = 0
반올림 ㅋ



이제 슈퍼마리오를 실행하면 된다.

오~ 된다.





...

그런데 또 잠깐.

반복 실행을 하다보면 인풋이 이상하게 들어와서 에러가 날 때가 있다.

그게 왜 들어오는진 모르겠지만 인풋이 이상하게 들어오면 그 프레임은 무시하고 넘어가자.

우선 ddqn_replay_train()에서 출력만 시키고 다음으로 넘어가자

        if state is None:
            print("None State, ", action, " , ", reward, " , ", next_state, " , ", done)
        else:
            원래 함수

을 추가하고



랜덤 플레이를 만드는 아래 부분에

                if np.random.rand(1) < e:
                    action = env.action_space.sample()

이렇게 좀 추가하자. 그냥 버그스럽게 이렇게 인풋이 들어올 때가 있다는 것만 기억하고.

                if np.random.rand(1) < e or state is None or state.size == 1:
                    action = env.action_space.sample()


이렇게 하면 드디어 슈퍼마리오가 실행 된다.

(효율 적으로 플레이 된다는 게 아니다. 그냥 에러 없이 실행 된다는 것 뿐)



학습을 하는지 마는지 몰라도 에러 없이 실행되긴 한다.
그냥 랜덤 입력으로 플레이 하는 것도 같지만 계속 켜놓으면 행동이 점점 변하긴 한다.
그게 좋은 쪽으로 변하는건지가 문제지만.



최종 코드 파일은 여기:
https://docs.google.com/document/d/16LLmgRPGZF2-wEa57uSbvwhDlnaPgyZLA9OnyImR2mQ/edit?usp=sharing

내가 고친 부분엔 ##### 표시를 해뒀다.



# original DQN 2015 source from  https://github.com/nalsil/kimhun_rl_windows/blob/master/07_3_dqn_2015_cartpole.py
# The code is updated to play super mario by Jinman Chang
# super mario game can be downloaded at https://github.com/ppaquette/gym-super-mario

# ##### is marked where is updated
# explanation for this code is at http://jinman190.blogspot.ca/2017/10/rl.html


###############################################################################super mario initialized
import gym
from gym.envs.registration import register
from gym.scoreboard.registration import add_group
from gym.scoreboard.registration import add_task
register(
     id='SuperMarioBros-1-1-v0',
     entry_point='gym.envs.ppaquette_gym_super_mario:MetaSuperMarioBrosEnv',
)

add_group(
     id='ppaquette_gym_super_mario',
     name='ppaquette_gym_super_mario',
     description='super_mario'
)

add_task(
    id='SuperMarioBros-1-1-v0',
    group='ppaquette_gym_super_mario',
    summary="SuperMarioBros-1-1-v0"
)
#################################################################################




import numpy as np
import tensorflow as tf
import random
from collections import deque

from gym import wrappers

env = gym.make('SuperMarioBros-1-1-v0')                                             #####update game title

# Constants defining our neural network
input_size = env.observation_space.shape[0]*env.observation_space.shape[1]*3        #####change input_size - 224*256*3 acquired from ppaquette_gym_super_mario/nes_env.py
output_size = 6                                                                     #####meaning of output can be found at ppaquette_gym_super_mario/wrappers/action_space.py

dis = 0.9
REPLAY_MEMORY = 50000

class DQN:
    def __init__(self, session, input_size, output_size, name="main"):
        self.session = session
        self.input_size = input_size
        self.output_size = output_size
        self.net_name = name

        self._build_network()

    def _build_network(self, h_size=10, l_rate=1e-1):
        with tf.variable_scope(self.net_name):
            self._X = tf.placeholder(tf.float32, [None, self.input_size], name="input_x")

            # First layer of weights
            W1 = tf.get_variable("W1", shape=[self.input_size, h_size],
                                 initializer=tf.contrib.layers.xavier_initializer())
            layer1 = tf.nn.tanh(tf.matmul(self._X, W1))

            # Second layer of Weights
            W2 = tf.get_variable("W2", shape=[h_size, self.output_size],
                                 initializer=tf.contrib.layers.xavier_initializer())

            # Q prediction
            self._Qpred = tf.matmul(layer1, W2)

        # We need to define the parts of the network needed for learning a policy
        self._Y = tf.placeholder(shape=[None, self.output_size], dtype=tf.float32)

        # Loss function
        self._loss = tf.reduce_mean(tf.square(self._Y - self._Qpred))
        # Learning
        self._train = tf.train.AdamOptimizer(learning_rate=l_rate).minimize(self._loss)

    def predict(self, state):
        x = np.reshape(state, [1, self.input_size])
        return self.session.run(self._Qpred, feed_dict={self._X: x})

    def update(self, x_stack, y_stack):
        return self.session.run([self._loss, self._train], feed_dict={self._X: x_stack, self._Y: y_stack})

def replay_train(mainDQN, targetDQN, train_batch):
    x_stack = np.empty(0).reshape(0, input_size)
    y_stack = np.empty(0).reshape(0, output_size)

    # Get stored information from the buffer
    for state, action, reward, next_state, done in train_batch:
        Q = mainDQN.predic(state)

        # terminal?
        if done:
            Q[0, action] = reward
        else:
            # get target from target DQN (Q')
            Q[0, action] = reward + dis * np.max(targetDQN.predict(next_state))

        y_stack = np.vstack([y_stack, Q])
        x_stack = np.vstack( [x_stack, state])

    # Train our network using target and predicted Q values on each episode
    return mainDQN.update(x_stack, y_stack)

def ddqn_replay_train(mainDQN, targetDQN, train_batch):
    '''
    Double DQN implementation
    :param mainDQN: main DQN
    :param targetDQN: target DQN
    :param train_batch: minibatch for train
    :return: loss
    '''
    x_stack = np.empty(0).reshape(0, mainDQN.input_size)
    y_stack = np.empty(0).reshape(0, mainDQN.output_size)

    # Get stored information from the buffer
    for state, action, reward, next_state, done in train_batch:
        if state is None:                                                               #####why does this happen?
            print("None State, ", action, " , ", reward, " , ", next_state, " , ", done)
        else:
            Q = mainDQN.predict(state)

            # terminal?
            if done:
                Q[0, action] = reward
            else:
                # Double DQN: y = r + gamma * targetDQN(s')[a] where
                # a = argmax(mainDQN(s'))
                # Q[0, action] = reward + dis * targetDQN.predict(next_state)[0, np.argmax(mainDQN.predict(next_state))]
                Q[0, action] = reward + dis * np.max(targetDQN.predict(next_state)) #####use normal one for now

            y_stack = np.vstack([y_stack, Q])
            x_stack = np.vstack([x_stack, state.reshape(-1, mainDQN.input_size)])   #####change shape to fit to super mario

    # Train our network using target and predicted Q values on each episode
    return mainDQN.update(x_stack, y_stack)

def get_copy_var_ops(*, dest_scope_name="target", src_scope_name="main"):

    # Copy variables src_scope to dest_scope
    op_holder = []

    src_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=src_scope_name)
    dest_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=dest_scope_name)

    for src_var, dest_var in zip(src_vars, dest_vars):
        op_holder.append(dest_var.assign(src_var.value()))

    return op_holder

def bot_play(mainDQN, env=env):
    # See our trained network in action
    state = env.reset()
    reward_sum = 0
    while True:
        env.render()
        action = np.argmax(mainDQN.predict(state))
        state, reward, done, _ = env.step(action)
        reward_sum += reward
        if done:
            print("Total score: {}".format(reward_sum))
            break

def main():
    max_episodes = 5000
    # store the previous observations in replay memory
    replay_buffer = deque()

    with tf.Session() as sess:
        mainDQN = DQN(sess, input_size, output_size, name="main")
        targetDQN = DQN(sess, input_size, output_size, name="target")
        tf.global_variables_initializer().run()

        #initial copy q_net -> target_net
        copy_ops = get_copy_var_ops(dest_scope_name="target", src_scope_name="main")
        sess.run(copy_ops)

        for episode in range(max_episodes):
            e = 1. / ((episode / 10) + 1)
            done = False
            step_count = 0
            state = env.reset()

            while not done:
                if np.random.rand(1) < e or state is None or state.size == 1:           #####why does this happen?
                    action = env.action_space.sample()
                else:
                    # Choose an action by greedily from the Q-network
                    #action = np.argmax(mainDQN.predict(state))
                    action = mainDQN.predict(state).flatten().tolist()                  #####flatten it and change it as a list
                    for i in range(len(action)):                                        #####the action list has to have only integer 1 or 0
                        if action[i] > 0.5 :
                            action[i] = 1                                               #####integer 1 only, no 1.0
                        else:
                            action[i] = 0                                               #####integer 0 only, no 0.0

                # Get new state and reward from environment
                next_state, reward, done, _ = env.step(action)
                if done: # Penalty
                    reward = -100

                # Save the experience to our buffer
                replay_buffer.append((state, action, reward, next_state, done))
                if len(replay_buffer) > REPLAY_MEMORY:
                      replay_buffer.popleft()

                state = next_state
                step_count += 1
                if step_count > 10000:   # Good enough. Let's move on
                    break

            print("Episode: {} steps: {}".format(episode, step_count))
            if step_count > 10000:
                pass
                # break

            if episode % 10 == 1: # train every 10 episode
                # Get a random batch of experiences
                for _ in range(50):
                    minibatch = random.sample(replay_buffer, 10)
                    loss, _ = ddqn_replay_train(mainDQN, targetDQN, minibatch)

                print("Loss: ", loss)
                # copy q_net -> target_net
                sess.run(copy_ops)

        # See our trained bot in action
        env2 = wrappers.Monitor(env, 'gym-results', force=True)

        for i in range(200):
            bot_play(mainDQN, env=env2)

        env2.close()
        # gym.upload("gym-results", api_key="sk_VT2wPcSSOylnlPORltmQ")

if __name__ == "__main__":
    main()












슈퍼마리오에 모두를 위한 RL 수업의 딥러닝 코드 붙이기

윈도우에서 openAI Gym을 이용해서 슈퍼마리오 AI 플레이 만들기 1. 윈도우 서브시스템 리눅스에 openAI Gym 설치하기 http://jinman190.blogspot.ca/2017/10/openai-gym.html 2. ope...