강화학습/OpenAI gym

OpenAI gym을 이용해 Deep SARSA 구현하기

APinCan 2020. 3. 23. 16:14

이번에는 신경망을 사용할건데 새로운 gym 환경인 OpenAI gym의 CartPole-v1을 사용함. 그리고 tensorflow를 설치해서 사용.

Deep SARSA의 경우 원래 구현했던 SARSA 알고리즘에서 state가 실제로 받을 값과 예측한 값을 신경망의 정답과 예측값으로 주어 학습시키는 방법임.

일단 카트폴 예제가 무엇인지 한번 보자.

바로 이런 카트와 폴이 있을 때 카트를 좌 우로 움직여 폴이 떨어지지 않게 하는게 카트폴 문제의 핵심이라고 할 수 있음.

 

Observation

0 : 카트의 위치

1 : 카트의 속도

2 : 폴의 각도

3 : 폴의 각속도

상태는 위와 같이 4개가 있고 각 상태마다 제한된 min max값이 있음. 여기서 확인

 

Action

0 : 카트를 왼쪽으로 밀기

1 : 카트를 오른쪽으로 밀기

 

Reward

각 타임스텝마다 +1, 종점에서도 똑같이 +1을 reward로 받음.

 

에피소드의 끝

폴의 각도가 12보다 커지거나

카트의 위치가 2.4가  되거나(카트의 중심부분이 화면의 끝에 닿음)

에피소드의 길이가 200보다 커지거나

100번의 연속적인 시도에서 평균 reward가 195.0보다 크거나 작으면 끝

 

근데 내가 구현한 코드는 에피소드의 끝을 나중에 확인해서 끝나는 조건이 좀 다름. 근데 어차피 reward가 195를 넘어간 적이 없으니 상관 없는 듯.

 

 

import gym
import random
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import optimizers

LEARNING_RATE=0.001
GAMMA=0.9
EPSILON=0.1

선언부분.

텐서플로가 새롭게 등장. 텐서플로를 이용해서 신경망을 구현하고 학습시켜보자.

 

env = gym.make("CartPole-v1")

카트폴 예제를 생성

 

def build_model(states_n, actions_n):
    model = keras.Sequential([
        keras.layers.Dense(32, activation=tf.nn.relu, input_shape=(states_n,)),
        keras.layers.Dense(64, activation=tf.nn.relu),
        keras.layers.Dense(actions_n)
    ])
    
    return model

모델 만들기

케라스를 이용해서 레이어를 쌓았음. 인풋의 경우 state의 개수, 출력의 경우 action의 개수로 state를 신경망에 넣고 돌리면 action에 대한 큐함수가 나오는 것. 이 전의 테이블형태 큐함수를 이렇게 신경망으로 근사시킨 것.

 

def action_select(state, q_function):
    actions = q_function
    rand_prob = random.random()
    action = 0
    
    max_val = np.max(actions)
    # max인 인덱스 저장
    max_list = [idx for idx, i in enumerate(actions) if i==max_val]
    # greedy
    if rand_prob >= EPSILON:
        action = random.choice(max_list)
    # non-greedy
    else:
        actions_idx= np.delete([0,1], max_list)
        if len(actions_idx)==0:
            action = 0
        else:
            action = random.choice(actions_idx)
        
    return action

어디서 많이 본 action_select 부분

action의 개수가 두개로 늘어난 것 이외에는 다른 부분이 별로 없음.

 

model = build_model(len(observation),actions_n)
model.summary()
model.compile(optimizer=optimizers.SGD(lr=LEARNING_RATE), loss='mse')

 

신경망을 만드는 부분.

build_model을 통해 신경망을 만들고 summary()로 내가 생각한대로 만들어졌는지 확인. 그리고 compile()로 loss function을 계산하는데는 mean squared error를 loss값을 줄이기 위해서는 optimizer로 stochastic gradient descent 알고리즘을 사용.

 

for epi in range(10000):
    print("epi", epi)
    observation = env.reset()
    
    with tf.device('/GPU:0'):
        for i in range(1000):
            # env.render()
        
            state_q = model.predict(observation.reshape(1,4))[0]
            action = action_select(observation, state_q)
        
            next_observation, reward, done, info = env.step(action)
        
            next_state_q = model.predict(next_observation.reshape(1,4))[0]
            next_action = action_select(next_observation, next_state_q)
        
            if done:
                state_q[action] = reward
            else:
                state_q[action] = reward + GAMMA*next_state_q[next_action] - state_q[action]

            model.fit(observation.reshape(1,4), state_q.reshape(1,2))
        
            if i>990:
                env.close()
    
            if done:
                print("break at {}".format(i+1))
                break
            
            observation = next_observation
            
env.close()

main 부분, 위에도 언급했다시피 문제가 끝나는 조건은 따로 있는데 나중에 확인해서 일단 이렇게 코드를 짰음.

state의 큐함수는 신경망으로 큐테이블을 근사시켰으니 predict를 통해 현재 state에 맞는 큐함수를 받고 거기서 \( \epsilohn \)-greedy를 적용해 action을 선택. 기본적으로 observation의 경우 [ x x x x ]이런형태인데 이걸 신경망에 그대로 넣으면 shape이 맞지 않아 에러가 나옴. 그래서 reshape을 진행시킴.

선택한 action으로 한 스텝 진행시키고 동일하게 진행. 만약 done이라면 그 다음 상태가 없으니 그냥 reward를 큐함수로 설정. 이전에는 이렇게 안했는데 참고한 책에서 이렇게 나와서 한 번 적용시켜봄. done이 아닌경우 SARSA에서 하던 것 처럼 큐함수를 계산. 이렇게 계산한 큐함수를 실제 받은 값으로 하고 observation을 통해 얻은 값을 예측값으로 해서 model.fit을 통해 학습시킴.

그리고 학습시킨 결과를 보자.

 

loss 값은 꽤 감소했는데 타임스텝은 진행되질 않음. 이게 의미하는 건 아마 optimal이 이상한 곳으로 수렴했다는 것 아닐까? 위의 값이 거의 모든 에피소드에서도 동일하게 나옴. 진짜 이상하게 학습한 듯? 아니 내가 코드를 이상하게 짠건가?

 

이거는 학습시키는 영상인데 저상태에서 더 이상 나아가질 않음. 

 

하여튼간에 Deep SARSA는 별로인 것 같고 나중에 DQN을 구현한다면 제대로 학습하지 않을까.

 

 

Reference : 파이썬과 케라스로 배우는 강화학습