Double DQN
논문 Deep Reinforcement Learning with Double Q-learning 으로 소개되었습니다.
DQN 에서 $Q$ 의 가치가 과대 평가되는 문제를 해결하기 위한 아이디어입니다. 타겟을 계산하는데 있어 DQN 과 차이가 있는데 Double DQN 은 일반 네트워크 $\theta$ 에서 행동 가치가 가장 높은 행동을 찾고 그 행동에 대한 가치는 타겟 네트워크 $\theta^-$ 에서 구합니다.
$Y_t^{\text{DQN}} \equiv R_{t+1} + \gamma {\underset{a} {\text{max}}}Q(S_{t+1}, a; \boldsymbol{\theta}_t^-)$
$Y_t^{\text{DoubleDQN}} \equiv R_{t+1} + \gamma Q(S_{t+1}, {\underset{a} {\text{argmax}}} Q(S_{t+1}, a; \boldsymbol{\theta}_t); \boldsymbol{\theta}_t^-)$
이는 DQN 과 DDQN 의 $Q$ 에 대해 최적 가치 $V_*(s)$ 와의 오차를 비교한 것입니다.
DQN 은 취할 수 있는 행동의 수가 증가함에 따라 최대 행동 가치는 더욱 과대 평가되어 오차는 증가합니다.
Dueling DQN
논문 Dueling Network Architectures for Deep Reinforcement Learning 으로 소개되었습니다.
행동 가치 함수 $Q$ 를 가치 함수 $V$ 와 어드밴티지 함수 $A$ 로 나눈다는 아이디어입니다. $A$ 를 이용하여 상태별 행동 가치에 대해 평균을 제하고 $V$ 를 더함으로써 행동 가치 함수 $Q$ 를 나타내는 것입니다.
$Q(s, a) = V(s) + A(s, a) - \frac{1}{|\mathcal{A}|}\sum_{a} A(s, a)$
구현
Double DQN 과 Dueling DQN 을 모두 포함하는 DDDQN 알고리즘을 구현합니다.
알고리즘 테스트 환경은 OpenAI Gym 의 LunarLander-v2 입니다.
LunarLander 는 드론을 깃발안에 안정적으로 착륙하는 문제입니다. 드론이 안정적인 착륙에 성공하면 좋은 보상을 받을 수 있으며 깃발 안쪽에 착륙한다면 더 큰 보상을 받습니다. 반대로 화면에서 사라지거나 착륙에 실패하면 나쁜 보상을 받게 됩니다.
환경으로부터 받을 수 있는 정보를 출력합니다.
import gym
env = gym.make('LunarLander-v2')
print('obs:', env.observation_space.shape[0])
print('act:', env.action_space.n)
상태 정보는 8차원의 벡터로 연속적인 공간을 갖으며 에이전트가 취할 수 있는 행동은 4개의 범위를 갖는 이산적인 공간입니다.
obs: 8
act: 4
신경망을 정의합니다.
class Network:
def __init__(self, scope):
with tf.variable_scope(scope):
with tf.name_scope('input'):
self.s = tf.placeholder(tf.float32, [None, N_S])
self.q_target = tf.placeholder(tf.float32, [None, N_A])
with tf.variable_scope('layer'):
self.fc1 = tf.layers.dense(self.s, 256, tf.nn.relu)
self.fc2 = tf.layers.dense(self.fc1, 128, tf.nn.relu)
self.v = tf.layers.dense(self.fc2, 1)
self.adv = tf.layers.dense(self.fc2, N_A)
with tf.variable_scope('output'):
self.q = self.v + (self.adv - tf.reduce_mean(self.adv, axis=1, keep_dims=True))
with tf.name_scope('loss'):
self.loss = tf.losses.mean_squared_error(self.q_target, self.q)
with tf.name_scope('optimizer'):
self.train_op = tf.train.RMSPropOptimizer(LR).minimize(self.loss)
self.params = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope)
Dueling DQN 에서 제안하는 구조로 층을 구성합니다.
with tf.variable_scope('layer'):
self.fc1 = tf.layers.dense(self.s, 256, tf.nn.relu)
self.fc2 = tf.layers.dense(self.fc1, 128, tf.nn.relu)
self.v = tf.layers.dense(self.fc2, 1)
self.adv = tf.layers.dense(self.fc2, N_A)
두 번째 층에서 $V$ 와 $A$ 의 층으로 연결되는 것을 볼 수 있습니다. 또한 $A$ 층의 출력이 행동의 크기입니다.
$V$ 와 평균을 제한 $A$ 를 합하여 $Q$ 를 나타냅니다.
with tf.variable_scope('out'):
self.q = self.v + (self.adv - tf.reduce_mean(self.adv, axis=1, keep_dims=True))
손실 함수를 정의합니다.
with tf.name_scope('loss'):
self.loss = tf.losses.mean_squared_error(self.q_target, self.q)
타겟과 예측 사이의 평균 제곱 오차(mean squared error, MSE)로 $Q$ 타겟과 추정 $Q$ 간의 계산입니다.
손실값을 최소화하는 방향으로 학습하도록 설정합니다.
with tf.name_scope('optimizer'):
self.train_op = tf.train.RMSPropOptimizer(LR).minimize(self.loss)
$\epsilon$-greedy 정책을 정의합니다.
self.epsilons = np.linspace(self.epsilon, 0.1, 200000)
...
def decay_epsilon(self):
self.epsilon = self.epsilons[min(self.train_cnt, 200000-1)]
def get_action(self, s):
if np.random.rand() < self.epsilon:
action = np.random.randint(N_A)
else:
q = self.sess.run(self.main.q, {self.main.s: s[np.newaxis, :]})
action = np.argmax(q)
return action
def train(self):
...
self.train_cnt += 1
self.decay_epsilon()
일반 신경망 업데이트 횟수가 200000번까지 $\epsilon$ 을 감소시키며 그 이후부터는 $\epsilon$ 을 0.1로 유지합니다.
일반 신경망을 업데이트합니다.
def train(self):
minibatch = random.sample(self.replay_memory, BATCH_SIZE)
minibatch = np.array(minibatch)
q = self.sess.run(self.main.q, {self.main.s: minibatch[:, 4].tolist()})
q_a = np.argmax(q, axis=1)
target_q = self.sess.run(self.target.q, {self.target.s: minibatch[:, 4].tolist()})
update_value = minibatch[:, 2] + GAMMA * target_q[range(len(minibatch)), q_a] * ~minibatch[:, 3].astype(np.bool)
q_target = self.sess.run(self.main.q, {self.main.s: minibatch[:, 0].tolist()})
q_target[range(len(minibatch)), minibatch[:, 1].tolist()] = update_value
_, loss = self.sess.run([self.main.train_op, self.main.loss], {self.main.s: minibatch[:, 0].tolist(),
self.main.q_target: q_target})
self.train_cnt += 1
self.decay_epsilon()
return loss
Double DQN 에서 제안한 식으로 타겟을 계산합니다.
q = self.sess.run(self.main.q, {self.main.s: minibatch[:, 4].tolist()})
q_a = np.argmax(q, axis=1)
target_q = self.sess.run(self.target.q, {self.target.s: minibatch[:, 4].tolist()})
update_value = minibatch[:, 2] + GAMMA * target_q[range(len(minibatch)), q_a] * ~minibatch[:, 3].astype(np.bool)
일반 신경망의 $Q_\theta$ 에서 최대의 행동 가치를 갖는 행동 시퀀스를 구하고 이를 이용하여 타겟 신경망의 $Q_{\theta^-}$ 에서 행동 가치를 구하여 계산합니다.
$Y_t^{\text{DoubleDQN}} \equiv R_{t+1} + \gamma Q(S_{t+1}, {\underset{a} {\text{argmax}}} Q(S_{t+1}, a; \boldsymbol{\theta}_t); \boldsymbol{\theta}_t^-)$
1000번의 에피소드 동안 학습한 결과입니다.
저장된 모델을 불러와 테스트합니다.
INFO:tensorflow:Restoring parameters from ./tmp/dddqn_lunarlander/model/model
episode: 0 / reward 276.7014293397136
episode: 1 / reward 22.280288275624372
episode: 2 / reward 231.75701431710345
episode: 3 / reward 215.13574884255914
episode: 4 / reward 235.63575783693358
episode: 5 / reward 283.90393140514414
episode: 6 / reward 285.0946617743626
episode: 7 / reward -62.710477314100785
episode: 8 / reward -28.79188154959367
episode: 9 / reward 34.11406993609501
'머신러닝 > 강화학습' 카테고리의 다른 글
액터-크리틱(Actor-Critic) (0) | 2020.11.27 |
---|---|
정책 그래디언트(Policy Gradient) (0) | 2020.11.24 |
DQN(Deep Q-Networks) (1) (0) | 2020.11.20 |
함수 근사(Function Approximation) (0) | 2020.11.18 |
Model-Free Control (0) | 2020.11.13 |