ML & DL

[강화 학습 PyTorch] Reinforcement Learning Tutorial (Part 2)

ssun-g 2022. 4. 30. 12:35

 

강화 학습 (Reinforcement Learning)

에이전트는 주어진 환경에서 행동을 선택하고, 그 환경에서 상태와 보상이 만들어진다.

에이전트의 목표는 주어진 환경에서 상태, 행동을 통해 얻어지는 보상을 최대화 하는 것.

 

에이전트는 보상을 최대화 하기 위해 어떤 선택이 가장 좋은 선택일지 학습하게 된다. 에이전트가 이런 과정으로 행동을 선택하는 것을 행동 정책(action policy)이라고 한다.

 

또한 에이전트가 행동을 선택하는 과정과 행동 정책을 구현하는 과정에서 Deep Q Network, epsilon-greedy 정책을 가장 흔히 사용한다.


Q-Learning

 

Q-table

Q-Learning은 값에 기반하여 어떤 행동을 선택하는 것이 좋을지 에이전트에게 알려주는 방식이다.

 

행동을 선택하기 위한 가장 직관적인 아이디어는 여러 번의 게임을 통해 어떤 행동을 선택 했을 때의 보상을 정리한 표를 만드는 것. 어떤 상태에서 어떠한 행동을 선택 했을 때 가장 보상이 큰 지 확인할 수 있다.

 

예를 들어, 네 가지 선택지(위, 아래, 왼쪽, 오른쪽)가 있는 게임을 생각해보자. 게임의 목표는 $에 도달하는 것이다. 이 게임의 모든 상황과 선택에 대한 보상을 다음과 같이 정리할 수 있다.

위 표를 통해 에이전트가 게임의 각 상황에서 어떤 선택을 하는 것이 가장 큰 보상으로 이어지는지 알 수 있다. 에이전트는 단순하게 이 표를 외우고 있다면 게임을 잘 할 수 있다.

 

지연된 보상

현실적인 게임을 잘 하려면 에이전트는 즉시 보상으로 이어지지 않고 큰 보상으로 이어질 가능성이 있는 행동을 선택할 줄 알아야 한다.

 

위 예시의 state2에서 아래 방향을 선택 했을 경우 즉시 15의 보상을 받게 되지만, 오른쪽 방향을 선택 했을 경우 더 큰 보상인 50을 기대할 수 있다.

 

따라서 에이전트는 즉시 받을 수 있는 보상보다 지연된 더 큰 보상을 받을 수 있는 선택을 할 줄 알아야 한다.

 

Q-Learning 룰

지연된 더 큰 보상을 받기 위해 Q-Learning의 룰을 정할 필요가 있다. Deep Q Learning에서 뉴럴 네트워크는 현재 상태 s를 입력으로 받아서, 선택할 수 있는 각각의 행동 a에 대한 값(Q-Value)을 반환

 

즉, 뉴럴 네트워크는 모든 상태 s에 대한 선택 a의 \(Q(s, a)\)값을 반환해야 한다. 해당 값은 훈련 과정에서 다음 룰을 통해 업데이트 된다.

 

$$ Q(s, a) ← Q(s, a) + \alpha[r + \gamma\max_{a'} Q(s', a') - Q(s, a)] $$

 

\(\alpha\) : 학습률 (Learning rate)

\(r\) : 상태 s에서 행동 a를 취함으로써 얻는 보상

\(\gamma\) : 지연된 보상의 효과를 줄이는 역할. 0 ~ 1 사이의 값을 갖는다.

\(\max_{a'} Q(s', a')\) : 모든 Q-Value의 최대값. (상태 s'에서의 모든 선택 a'에 대한 Q-Value의 최대값)


Deep Q-Learning

Deep Q-Learning은 학습 과정에서 Q-Learning의 업데이트 룰을 적용한다.

 

현재 상태 s를 입력으로 받는 뉴럴 네트워크를 구성하고, 뉴럴 네트워크가 상태 s의 모든 행동 a에 대해 적절한 \(Q(s, a)\) 값을 출력하도록 훈련 시킨다. 훈련이 끝나면 에이전트가 가장 큰 \(Q(s, a)\) 값을 나타내는 행동 a를 선택할 수 있다.

 

에이전트가 행동을 선택하고 나면, 그 상태에서 그 행동을 취함으로써 얻는 보상이 결정된다. 행동 a에 대한 \(Q(s, a)\)값에 대해서만 \(r + \gamma Q(s', a')\)를 목표로 훈련이 이루어 진다.

 

이러한 방식으로 네트워크를 훈련하고 나면, 출력되는 \(Q(s, a)\) 값들은 해당 상태에서 어떤 행동이 가장 좋은 선택인지 에이전트에게 알려줄 수 있게 된다.


Epsilon-greedy 정책

이전까지의 내용에서 행동을 선택하는 정책은 단순히 네트워크가 가장 높은 \(Q(s, a)\)를 출력하는 행동 a를 선택하는 것.

하지만 이 정책은 가장 효과적인 방식이 아닐 수 있다. 뉴럴 네트워크는 처음에 무작위로 초기화 되고 차선의 행동들을 임의로 선택할 가능성이 있기 때문.

 

다시 말해, 에이전트가 전체 게임의 모든 상태-행동-보상 공간을 탐색하지 못하고 최선이 아닌 차선의 선택들을 하게 될 수 있다. 즉, 게임의 가장 좋은 전략을 찾지 못하는 것을 의미한다.

 

탐색과 활용

탐색(exporation)활용(exploitation)이라는 두 가지 개념을 살펴보자.

 

최적화 문제의 시작점에서 좋은 local minima 또는 가능 하다면 global minima를 찾기 위해 주어진 공간을 광범위하게 탐색하는 것이 좋다.

 

하지만 공간을 적절하게 한 번 탐색하고 나면 발견한 것들을 잘 활용해 minima를 찾는 최적화 알고리즘이 좋은 알고리즘이라고 볼 수 있다.

 

 

따라서 강화학습에는 행동을 선택하는데 있어서 약간의 임의성(randomness) \(\epsilon\)을 부여한다.

 

만약 0 ~ 1 사이의 임의의 숫자를 얻었을 때, \(\epsilon\)보다 작다면, 임의의 행동을 선택하고 \(\epsilon\)보다 크다면 네트워크의 출력값에 근거해 행동을 선택한다. \(\epsilon\)은 학습 초반에 값이 크고 학습이 진행 될수록 0에 가까운 값으로 작아진다.

 

즉, 학습 초반에 더 많은 가능성들을 탐색하고, 학습이 어느정도 진행된 이후에는 이를 활용해 네트워크가 좋은 정답을 출력할 수 있도록 한다.


첫 번째 훈련

  • 필요한 module import
import gym
import tensorflow as tf
import numpy as np
import random
from collections import deque

 

  • 훈련에 사용될 뉴럴 네트워크 모델 정의.
model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(24, input_dim=4, activation=tf.nn.relu),
    tf.keras.layers.Dense(24, activation=tf.nn.relu),
    tf.keras.layers.Dense(2, activation='linear')
])

 

  • 모델 컴파일
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3), loss='mean_squared_error')

모델의 훈련 방식, 손실함수 등을 정의한다.

 

  • CartPole 환경 구성
score = []
memory = deque(maxlen=2000)

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

score : 각 에피소드의 점수 저장

memory : 현재 상태와 행동, 다음 상태, 보상 등을 보관

 

  • 에피소드 시작 (1000회)
for i in range(1000):

  state = env.reset()
  state = np.reshape(state, [1, 4])
  eps = 1 / (i / 50 + 10)

각 에피소드마다 환경을 초기화 하고, 뉴럴 네트워크의 입력에 맞는 shape로 변환.

eps(epsilon)는 에피소드가 진행 될수록 탐색보다 활용의 비율이 높아진다.

 

  • CartPole time step 
for t in range(200):

    # Inference: e-greedy
    if np.random.rand() < eps:
        action = np.random.randint(0, 2)
    else:
        predict = model.predict(state)
        action = np.argmax(predict)

각 에피소드마다 200 time step 동안 진행된다.

각 time step에서 매 번 행동을 선택하는데, eps 값에 따라 임의의 선택을 하거나 뉴럴 네트워크의 선택을 따른다.

 

  • next_state 
next_state, reward, done, _ = env.step(action)
next_state = np.reshape(next_state, [1, 4])  # Network에 입력하기 위해 shape 변경

memory.append((state, action, reward, next_state, done))
state = next_state

선택한 행동(action)에 따라 다음 상태, 보상, done 값이 반환됨.

다음 상태(next_state)는 다시 뉴럴 네트워크에 입력하기 위해 shape를 변경.

현재 상태, 행동, 보상, 다음 상태, 보상을 memory에 저장 후, 다음 상태로 이동한다.

 

  • 에피소드의 종료 조건
if done or t == 499:
    print('Episode', i, 'Score', t + 1)
    score.append(t + 1)
    break

500 time step이 지나거나 done == True인 경우, 점수를 저장하고 에피소드 종료.

 

  • 에피소드를 10회 이상 진행하는 경우
if i > 10:
    minibatch = random.sample(memory, 16)

    for state, action, reward, next_state, done in minibatch:
        target = reward
        if not done:
            target = reward + 0.9 * np.amax(model.predict(next_state)[0])
        target_outputs = model.predict(state)
        target_outputs[0][action] = target
        model.fit(state, target_outputs, epochs=1, verbose=0)

Q-Learning 룰에 따라 Q-Value(target)을 설정하고, 현재 상태에서 뉴럴 네트워크가 출력해야 할 값(target_outputs)들을 설정한다.

 

  • 게임 종료
env.close()
print(score)

게임을 종료하고 에피소드에 따른 점수 출력.

 

결과는 다음과 같다.

에피소드에 따른 점수 결과(cartpole-v0)

 

  • 전체 코드
import gym
import tensorflow as tf
import numpy as np
import random
from collections import deque

# 뉴럴 네트워크 모델 만들기
model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(24, input_dim=4, activation=tf.nn.relu),
    tf.keras.layers.Dense(24, activation=tf.nn.relu),
    tf.keras.layers.Dense(2, activation='linear')
])

# 모델 컴파일
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3), loss='mean_squared_error')

score = []
memory = deque(maxlen=2000)

# CartPole 환경 구성
env = gym.make('CartPole-v1')

# 1000회의 에피소드 시작
for i in range(1000):

    state = env.reset()
    state = np.reshape(state, [1, 4])
    eps = 1 / (i / 50 + 10)

    # 500 timesteps
    for t in range(500):
    
    	env.render()  # 게임 화면 출력

        # Inference: e-greedy
        if np.random.rand() < eps:
            action = np.random.randint(0, 2)
        else:
            predict = model.predict(state)
            action = np.argmax(predict)

        next_state, reward, done, _ = env.step(action)
        next_state = np.reshape(next_state, [1, 4])

        memory.append((state, action, reward, next_state, done))
        state = next_state

        if done or t == 499:
            print('Episode', i, 'Score', t + 1)
            score.append(t + 1)
            break

    # Training
    if i > 10:
        minibatch = random.sample(memory, 16)

        for state, action, reward, next_state, done in minibatch:
            target = reward
            if not done:
                target = reward + 0.9 * np.amax(model.predict(next_state)[0])
            target_outputs = model.predict(state)
            target_outputs[0][action] = target
            model.fit(state, target_outputs, epochs=1, verbose=0)

env.close()
print(score)

 

<pytorch version>

cartpole_pytorch.ipynb
0.06MB


Epsilon의 영향

 

epsilon-greedy 정책의 사용 유무에 따른 결과(cartpole-v0)

 

훈련 과정에서 탐색과 활용의 적절한 사용이 결과에 좋은 영향을 미치는 것을 확인할 수 있다. 즉, 훈련 과정에서는 이미 알고 있는 것과 다른 선택을 가끔씩 하는 것이 더 효과적임을 의미한다.

 


📌 Reference

 

강화학습 시작하기 (CartPole 게임) - Codetorial

Gym 라이브러리 Gym은 강화학습 알고리즘을 개발하고 비교하기 위한 툴킷입니다. agent의 구조에 대해서 어떠한 가정을 하지 않으며, TensorFlow와 Theano와 같은 라이브러리와 호환 가능합니다. Gym 라

codetorial.net