OpenAI Gym으로 Q-table 알고리즘 만들기 2

Q-러닝(Q-Learning)은 인공지능 “에이전트”가 구축할 환경의 모델을 알 필요도, 가질 필요도 없다는 점에서, 모델 없이 학습하는 강화 학습 기법 가운데 하나입니다. 다양한 환경에서 동일한 알고리즘을 사용할 수 있습니다.

주어진 환경(Environment)에서 모든 것은 “상태(states)”와 “조치(actions)”로 분류됩니다. states는 우리가 환경에서 가져온 관찰(observations, Interpreter) 및 샘플링이며, actions은 관찰에 따라 에이전트가 내린 선택입니다. 

출처:https://en.wikipedia.org 강화 학습(RL) 시나리오의 일반적인 프레임: 에이전트는 환경에서 조치를 취하는데, 이는 에이전트로 다시 피드백되는 보상과 상태의 표현으로 해석된다.

OpenAI Gym으로 “MountainCar-v0“환경을 사용해보겠습니다. Gym을 설치하고  환경을 확인해 보겠습니다. 이러한 기본 Gym 환경의 대부분은 작동 방식이 매우 동일합니다. 환경을 초기화하기 위해 gym.make(NAME)를 실행한 다음에 반복할 때마다 env.step(ACTION)를 실행합니다.

import gym

env = gym.make("MountainCar-v0")
print(env.action_space.n)

Gym의 다양한 환경에서 가능한 액션/이동 횟수를 조회할 수 있습니다. action_space.n 값으로 확인할 수 있는데 MountainCar-v0의 경우에 3개의 액션이 있습니다. 즉, 환경을 단계적으로 수행 할 때 각 단계의 “액션” 으로 0, 1 또는 2를 사용할 수 있습니다. 이 작업을 수행 할 때마다 환경은 새로운 상태, 보상, 환경 수행 / 완료 여부, 일부 envs가 가질 수 있는 추가 정보를 반환할 것입니다.

0은 왼쪽으로 밀고 1은 가만히 있고 2는 오른쪽으로 밀고 있다는 의미입니다. 이런 의미를 모델에 알리지 않아도 되는데 이것이 Q러닝의 장점입니다. 모델이 알아야 할 것은 액션에 대한 옵션이 무엇인지, 그리고 그러한 액션을 수행하는 것에 대한 보상에 어떤 상태가 부여되는지에 대한 것입니다.

import gym

env = gym.make("MountainCar-v0")
env.reset()

done = False
while not done:
    action = 2  # 자동차가 항상 오른쪽으로 움직이게 한다.
    env.step(action)
    env.render()

실행 결과를 보면 자동차를 계속 오른쪽으로 이동하게 했음에도 불구하고, 우리는 자동차가 그것을 만들 힘이 충분하지 않다는 것을 알 수 있습니다. 깃발에 도달하기 위한 운동량이 필요합니다. 이를 위해 운동량을 늘리기 위해 앞뒤로 움직이게 해야 합니다. 이 작업을 수행하는 함수를 프로그래밍하거나 Q-러닝을 사용하여 해결할 수 있습니다!

Q-러닝을 사용하여 어떻게 해결할 수 있을까요? 우리는 주어진 시간에 3가지 조치(action)를 취할 수 있다는 것을 알고 있습니다. 그것은 action_space.n 값으로 알 수 있었습니다. 이제 “observation space”이 필요합니다. 이 Gym 환경의 경우 reset 및 step에서 observation 값이 반환됩니다. 예를 들면 다음과 같습니다.

import gym

env = gym.make("MountainCar-v0")
print(env.reset())

시작할 때의 observation state 인 [-0.4826636 0.]과 같은 정보를 제공합니다. 환경이 실행되는 동안 다음과 같은 정보도 얻을 수 있습니다.

import gym

env = gym.make("MountainCar-v0")
state = env.reset()

done = False
while not done:
    action = 2
    new_state, reward, done, _ = env.step(action)
    print(reward, new_state)

각 step에서, 우리는 환경의 완료 여부, 새로운 상태(new_state), 보상을 얻은 다음 최종 “추가 정보”가 반환됩니다. Gym은 실제로 코드를 변경하지 않고도 다양한 환경에서 동일한 강화 학습 프로그램을 사용할 수 있도록  제공합니다.

위 프로그램의 출력은 다음과 같습니다.

-1.0 [-0.26919024 -0.00052001]
-1.0 [-0.27043839 -0.00124815]
-1.0 [-0.2724079  -0.00196951]
-1.0 [-0.27508804 -0.00268013]
-1.0 [-0.27846408 -0.00337604]
-1.0 [-0.28251734 -0.00405326]
-1.0 [-0.28722515 -0.00470781]
-1.0 [-0.29256087 -0.00533573]
-1.0 [-0.29849394 -0.00593307]

우리는 보상이 항상 -1이라는 것을 알 수 있습니다. 그리고 우리는 observation이 두 가지 값이라는 것을 알고 있습니다. 이 값은 위치 (x / 가로 축을 따라)와 속도 입니다. 예를 들어 속도가 음수이기 때문에 자동차가 왼쪽으로 움직이는 것을 볼 수 있습니다. 

위치와 속도를 사용하면 우리는 자동차가 깃발에 도달할 수 있을지 없을지를 계산할 수 있는 어떤 종류의 알고리즘을 고안해 낼 수 있는데 Q러닝도 똑같이 할 수 있습니다. observation space는 어떤 크기라도 가능하지만 크기가 커지면 Q테이블은 훨씬 커집니다!

Q테이블이란

Q-Learning이 작동하는 방식은 상태 당 가능한 조치(action)에 대응하는  “Q”값이 있다는 것입니다. 이것으로 테이블을 만드는 것입니다. 가능한 모든 상태를 파악하기 위해서 환경을 조사하거나 환경에 관여해야 합니다.

출처:https://en.wikipedia.org Q러닝 테이블은 0으로 초기화되었다가 훈련(학습)을 통해 각각의 셀은 업데이트됩니다.

이 경우 환경을 조회하여 이러한 각 상태 값의 가능한 범위를 찾을 수 있습니다.

print(env.observation_space.high)
print(env.observation_space.low)
[0.6  0.07]
[-1.2  -0.07]

인덱스 0의 값에 대해 높은 값은 0.6이고, 낮은 값은 -1.2이며, 인덱스 1의 값에 대해서는 높은 값은 0.07이고, 낮은 값은 -0.07입니다. 자, 이것이 범위이지만, 위의 observation state 결과 중 하나에서 우리가 출력 한 것 : [-0.27508804 -0.00268013]에서, 우리는이 숫자들이 상당히 세분화 될 수 있음을 알 수 있습니다. 이 범위의 모든 조합에 대해 소수점 이하 8 자리까지 값을 가지려는 경우 Q 테이블의 크기를 상상할 수 있습니까? 그렇게 세분화 될 필요는 없습니다. 대신, 우리가하고 싶은 것은이 연속적인 값을 이산 값으로 수렴하는 것입니다. 기본적으로 우리는 범위를보다 관리하기 쉬운 것으로 버킷 / 그룹화하려고합니다.

각 범위에 20 개의 그룹 / 버킷을 사용합니다. 이것은 나중에 수정할 수 있는 변수입니다.

DISCRETE_OS_SIZE = [20, 20]
discrete_os_win_size = (env.observation_space.high - env.observation_space.low) / DISCRETE_OS_SIZE


print(discrete_os_win_size)
[0.09  0.007]

이것은 각각의 버킷이 얼마나 큰지, 기본적으로 각 버킷의 범위를 얼마나 증가시킬지 알려줍니다. 이제 다음을 통해 q_table을 구축할 수 있습니다.

import numpy as np

...

q_table = np.random.uniform(low=-2, high=0, size=(DISCRETE_OS_SIZE + [env.action_space.n]))

따라서 이것은 20x20x3 모양이며, 우리에게 필요한 임의의 Q 값으로 초기화했습니다. 20 x 20 비트는 모든 가능한 상태의 버킷 슬라이스의 모든 조합입니다. x3 비트는 우리가 취할 수 있는 모든 가능한 액션(0,1,2)에 대한 것입니다.

여기서 볼 수 있듯이 간단한 환경에도 꽤 큰 테이블이 필요하게 됩니다. 우리는 모든 가능한 상태에 필요한 값을 갖게 됩니다.

따라서 이러한 값들은 임의적이며 -2와 0사이에서 선택되는 것도 변수입니다. 각각의 스탭은 보상이 -1이고 깃발은 보상이 0이므로 임의의 Q 값의 시작점을 모두 음수로 만드는 것이 합리적입니다.

이 테이블은 중요합니다. 우리는 움직임을 결정하기 위해 이 테이블을 참고할 것입니다. 마지막 x3은 우리의 3 가지 액션이며, 그 3 가지 액션 각각은 관련된 “Q 값”을가집니다.

가능한 모든 이산 상태를 포함하는 Q-Table을 만듭니다. 다음으로 Q-Values (고유 상태별 가능한 액션 값)를 업데이트하는 방법이 필요합니다.

위의 공식을 코드로 작성해보면 다음과 같습니다.

new_q = (1 - LEARNING_RATE) * current_q + LEARNING_RATE * (reward + DISCOUNT * max_future_q)

위 코드에서 지금 알 수 없는 변수는 다음과 같습니다. DISCOUNT, max_future_q

DISCOUNT즉각적인 보상보다는 미래 보상에 얼마나 관심이 있는지를 측정하는 척도입니다. 일반적으로 이 값은 상당히 높을 것이고 0에서 1 사이입니다. Q Learning의 목적은 실제로 긍정적인 결과로 끝나는 일련의 사건을 배우는 것이기 때문에 우리는 그것을 높게 원하기 때문에 단기적인 이득보다는 장기적인 이득에 더 큰 중요성을 부여하는 것이 당연합니다.

max_future_q는 작업을 이미 수행 한 후  다음 단계의 최고 Q 값을 기반으로 이전 값을 부분적으로 업데이트합니다. 시간이 지남에 따라 목표에 한 번 도달하면 이 “보상”값이 에피소드 당 한 번에 한 단계 씩 천천히 역전파됩니다. 

import gym
import numpy as np

env = gym.make("MountainCar-v0")
env.reset()

DISCRETE_OS_SIZE = [20, 20]
discrete_os_win_size = (env.observation_space.high - env.observation_space.low)/DISCRETE_OS_SIZE

q_table = np.random.uniform(low=-2, high=0, size=(DISCRETE_OS_SIZE + [env.action_space.n]))

done = False

while not done:
    action = 2
    new_state, reward, done, _ = env.step(action)
    print(reward, new_state)
    env.render()
    #new_q = (1 - LEARNING_RATE) * current_q + LEARNING_RATE * (reward + DISCOUNT * max_future_q)

이제 상수를 더 추가해 봅시다.

# Q-Learning settings
LEARNING_RATE = 0.1
DISCOUNT = 0.95
EPISODES = 25000

LEARNING_RATE는 0과 1 사이의 값을 사용합니다. EPISODES는 우리가 실행하고자 하는 게임의 반복 횟수입니다.

다음으로 헬퍼 함수가 필요합니다.

def get_discrete_state(state):
    discrete_state = (state -env.observation_space.low)/discrete_os_win_size
    return tuple(discrete_state.astype(np.int))

코드를 다시 정리해보면 다음과 같습니다.

import gym
import numpy as np

env = gym.make("MountainCar-v0")

LEARNING_RATE = 0.1

DISCOUNT = 0.95
EPISODES = 25000

DISCRETE_OS_SIZE = [20, 20]
discrete_os_win_size = (env.observation_space.high - env.observation_space.low)/DISCRETE_OS_SIZE

q_table = np.random.uniform(low=-2, high=0, size=(DISCRETE_OS_SIZE + [env.action_space.n]))


def get_discrete_state(state):
    discrete_state = (state - env.observation_space.low)/discrete_os_win_size
    return tuple(discrete_state.astype(np.int))  # we use this tuple to look up the 3 Q values for the available actions in the q-table


discrete_state = get_discrete_state(env.reset()) #초기 상태값을 가져옵니다.
done = False
while not done:

    action = np.argmax(q_table[discrete_state]) #처음 예제에서 action = 2
    new_state, reward, done, _ = env.step(action)

    new_discrete_state = get_discrete_state(new_state) #새로운 상태값

    env.render()
    #new_q = (1 - LEARNING_RATE) * current_q + LEARNING_RATE * (reward + DISCOUNT * max_future_q)

#이제 Q-값을 업데이트합니다. 이미 만든 액션에 대한 Q값을 업데이트하고 있다는 점에 유의하십시오. # If simulation did not end yet after last step - update Q table if not done: # Maximum possible Q value in next step (for new state) max_future_q = np.max(q_table[new_discrete_state]) # Current Q value (for current state and performed action) current_q = q_table[discrete_state + (action,)] # And here's our equation for a new Q value for current state and action new_q = (1 - LEARNING_RATE) * current_q + LEARNING_RATE * (reward + DISCOUNT * max_future_q) # Update Q table with new Q value q_table[discrete_state + (action,)] = new_q # Simulation ended (for any reson) - if goal position is achived - update Q value with reward directly elif new_state[0] >= env.goal_position: #q_table[discrete_state + (action,)] = reward q_table[discrete_state + (action,)] = 0 discrete_state = new_discrete_state env.close()

Gym FrozenLake 환경

FrozenLake 환경을 해결하기 위해 Q-테이블 알고리즘을 만들어 보겠습니다. 이 환경에서 목표는 중간에 얼음 구멍이 있는 얼어 붙은 호수에서 얼음 구멍을 피해서 목표에 도달하는 것입니다. 이 알고리즘으로 호수 표면을 묘사하는 방법은 다음과 같습니다.

SFFF       (S: 출발점, 안전한 곳)
FHFH (F: 얼어있는 표면, 안전한 곳)
FFFH (H: 구멍, 게임 실패)
HFFG (G: 목표, 게임 성공)

state-action은 보상에 매핑되어 있고 이것을 Q 테이블에 포함하고 있습니다. 따라서 알고리즘 실행 중에 다른 상태와 액션을 보상값에 매핑하는 배열을 구성합니다. 이때 차원 값은 상태와 액션의 곱하기입니다.  Q-러닝 알고리즘을 위한 코드는 다음과 같습니다.

import gym
import numpy as np

# 1. Load Environment and Q-table structure 환경을 로드하고 Q테이블 조직하기
env = gym.make('FrozenLake8x8-v0')
Q = np.zeros([env.observation_space.n,env.action_space.n]) #배열 만들기
# env.obeservation.n, env.action_space.n gives number of states and action in env loaded 환경이 로드될 때의 상태와 액션의 수

# 2. Parameters of Q-leanring Q러닝 변수
eta = .628
gma = .9
epis = 5000 #에피소드: 실행하고자 하는 게임의 반복 횟수
rev_list = [] # rewards per episode calculate

# 3. Q-learning Algorithm Q러닝 알고리즘

for i in range(epis):
# Reset environment 환경 재설정하기
s = env.reset()
rAll = 0
d = False
j = 0
#The Q-Table learning algorithm Q테이블 러닝 알고리즘
while j < 99:
env.render()
j+=1
# Choose action from Q table Q테이블에서 액션 선택하기
a = np.argmax(Q[s,:] + np.random.randn(1,env.action_space.n)*(1./(i+1)))
#Get new state & reward from environment 환경에서 새로운 상태와 보상 얻기
s1,r,d,_ = env.step(a)
#Update Q-Table with new knowledge 새로운 Q함수 방정식으로 Q테이블 수정하기
Q[s,a] = Q[s,a] + eta*(r + gma*np.max(Q[s1,:]) - Q[s,a])
rAll += r
s = s1
if d == True:
break
rev_list.append(rAll)
env.render()

print ("Reward Sum on all episodes ") + str(sum(rev_list)/epis)
print ("Final Values Q-Table")
print (Q)

* Q function Equation

tells about maximum expected cumulative award for given pair.

환경을 통해 솔루션을 찾기 위한 에이전트 시뮬레이션에 관심이 있다면 Q- 러닝 알고리즘 대신에 아래 간단한 코드를 사용하세요.

 

 

 

# Reset environment
s = env.reset()
d = False
# The Q-Table learning algorithm
while d != True:
env.render()
# Choose action from Q table
a = np.argmax(Q[s,:] + np.random.randn(1,env.action_space.n)*(1./(i+1)))
#Get new state & reward from environment
s1,r,d,_ = env.step(a)
#Update Q-Table with new knowledge
Q[s,a] = Q[s,a] + eta*(r + gma*np.max(Q[s1,:]) - Q[s,a])
s = s1
# Code will stop at d == True, and render one state before it

그러나 공통 인터페이스를 사용하더라도 코드 복잡성은 환경에 따라 다를 수 있음을 기억하세요. 위의 환경에서는 처리할 액션(에이전트의 이동으로 4개)이 거의 없는 단순한 64개 상태 환경만 있었습니다. 보상(reward) 매핑을 위해 2차원 배열로 매우 쉽게 저장할 수 있었습니다. 이제 Atari envs와 같은 더 복잡한 환경 사례를 고려하고 필요한 접근 방식을 살펴 보겠습니다.

env = gym.make("Breakout-v0")
env.action_space.n
Out[...]: 4
env.env.get_action_meanings()
Out[...]: ['NOOP', 'FIRE', 'RIGHT', 'LEFT']
env.observation_space
Out[...]: Box(210, 160, 3)

observation_space은 210x160x3 텐서로 표현되어야 하며, 이는 Q 테이블을 더욱 복잡하게 만듭니다. 또한 각 action은 k 프레임의 지속 기간 동안 반복적으로 수행되며, k는 2,3,4에서 균일하게 샘플링됩니다. 0-255 범위의 값을 가진 RGB 채널에서 33,600 픽셀의 환경에서는 복잡한 Q-러닝 방식을 사용할 수 없습니다. 이 경우에는 CNN(Convolutional Neural Network) 아키텍처를 사용한 딥 러닝이 문제에 대한 솔루션입니다.

OpenAI 참고

강화 학습 참고