자료를 공개한 저자 오렐리앙 제롱과 강의자료를 지원한 한빛아카데미에게 진심어린 감사를 전합니다.
$V^*(s)$: 상태 $s$ 에서 에이전트가 최적의 행동을 선택한다고 가정했을 때 얻을 수 있는 할인된 미래 보상에 대한 기대치의 최댓값
$$ V^*(s) = \max_a \sum_{s'} T(s, a, s')\cdot \big [R(s, a, s') + \gamma \cdot V(s') \big] $$
동적계획법 활용: $V^*(s)$를 동적계획법으로 빠르게 계산 가능
$$ V_{k+1}^*(s) \leftarrow \max_a \sum_{s'} T(s, a, s')\cdot \big [R(s, a, s') + \gamma \cdot V_k(s') \big] $$
동적계획법 활용: $Q^*(a, s)$를 동적계획법으로 빠르게 계산 가능
$$ Q_{k+1}^*(a, s) \leftarrow \sum_{s'} T(s, a, s')\cdot \big [R(s, a, s') + \gamma \cdot \max_{a'}Q_k(s', a') \big] $$
$\pi^*(s)$: 상태 $s$에 도착했을 때 취할 수 있는 최선의 정책은 최고의 Q-가치를 갖는 행동 선택하기
$$ \textrm{argmax}_a\, Q^*(s, a) $$
transition_probabilities = [ # 모양=[s, a, s']
[[0.7, 0.3, 0.0], [1.0, 0.0, 0.0], [0.8, 0.2, 0.0]],
[[0.0, 1.0, 0.0], None, [0.0, 0.0, 1.0]],
[None, [0.8, 0.1, 0.1], None]]
rewards = [ # 모양=[s, a, s']
[[+10, 0, 0], [0, 0, 0], [0, 0, 0]],
[[0, 0, 0], [0, 0, 0], [0, 0, -50]],
[[0, 0, 0], [+40, 0, 0], [0, 0, 0]]]
possible_actions = [[0, 1, 2], [0, 2], [1]]
Q_values = np.full((3, 3), -np.inf) # 불가능한 행동: -np.inf
for state, actions in enumerate(possible_actions):
Q_values[state, actions] = 0.0 # 가능한 행동: 0
gamma
= 0.90for iteration in range(50):
Q_prev = Q_values.copy()
for s in range(3):
for a in possible_actions[s]:
Q_values[s, a] = np.sum([
transition_probabilities[s][a][sp]
* (rewards[s][a][sp] + gamma * np.max(Q_prev[sp]))
for sp in range(3)])
array([[18.91891892, 17.02702702, 13.62162162],
[ 0. , -inf, -4.87971488],
[ -inf, 50.13365013, -inf]])
gamma=0.90
인 경우: np.argmax(Q_values, axis=1)
array([0, 0, 1])
gamma=0.95
인 경우: np.argmax(Q_values, axis=1)
상태 $s_1$에서 당장의 고통(불길, -50)을 감수하고 행동 $a_2$ 선택
array([0, 2, 1])
탐험이 진행하면서 실제로 관측된 전이와 보상에 근거하여 상태 가치 추정값 업데이트
$$ V_{k+1}(s) \leftarrow (1-\alpha) V_k(s) + \alpha \big( r + \gamma\cdot V_k(s') \big) $$
아래와 같이 표현 가능:
$$ V_{k+1}(s) \leftarrow V_k(s) + \alpha \cdot \delta_k(s, r, s') $$
단,
$$ \delta_k(s, r, s') = r + \gamma\cdot V_k(s') - V_k(s) $$
아래 식을
$$ V_{k+1}(s) \leftarrow (1-\alpha) V_k(s) + \alpha \big( r + \gamma\cdot V_k(s') \big) $$
다음과 같이 표현하는 것 선호됨
$$ V(s) \,\underset{\alpha}{\leftarrow}\, r + \gamma\cdot V(s') $$
TD 학습 방식을 Q-가치를 추청하는 데에 사용함.
$$ Q(s, a) \,\underset{\alpha}{\leftarrow}\, r + \gamma\cdot \max_{a'} Q_(s', a') $$
이전에 많이 시도하지 않았던 행동을 시도하도록 유도하는 정책
$$ Q(s, a) \,\underset{\alpha}{\leftarrow}\, r + \gamma\cdot \max_{a'} f(Q_(s', a'), N(s',a')) $$
$N(s',a')$: 상태 $s'$에서 행동 $a'$을 선택한 횟수
$f(Q, N)$은 아래와 같은 탐험 함수
$$f(Q, N) = Q + \frac{\kappa}{1+N}$$
행동을 결정해야 하는 매 순간(상태)에 이전 경험을 바탕으로 정해진 타깃 Q-가치를 목표로 지도학습 실행
타깃 Q-가치는 정해진 배치(batch) 크기 만큼 무작위적으로 선택된 이전 경험으로 결정.
$$ Q_{\textit{target}}(s, a) = r + \gamma\cdot \max_{a'} Q_\theta(s', a') $$
다수의 에피소드를 통한 업데이트 반복 알고리즘으로 이해하려면 아래 식이 보다 적절함. $Q_{\textit{target}}(s, a)$은 매 에피소드마다 업데이트됨.
$$ Q_{\textit{target}}(s, a) \leftarrow r + \gamma\cdot \max_{a'} Q_\theta(s', a') $$
env = gym.make("CartPole-v1")
input_shape = [4] # 관측 자료형 모양
n_outputs = 2 # 행동 종류 2개
# 출력층 뉴련 수: 2개.
# 즉, 현재 상태에서 취할 수 있는 모든 행동에 대한 확률값 반환
model = keras.models.Sequential([
keras.layers.Dense(32, activation="elu", input_shape=input_shape),
keras.layers.Dense(32, activation="elu"),
keras.layers.Dense(n_outputs)
])
def epsilon_greedy_policy(state, epsilon=0):
if np.random.rand() < epsilon:
return np.random.randint(2)
else:
Q_values = model.predict(state[np.newaxis])
return np.argmax(Q_values[0])
def sample_experiences(batch_size):
indices = np.random.randint(len(replay_memory), size=batch_size)
batch = [replay_memory[index] for index in indices]
states, actions, rewards, next_states, dones = [
np.array([experience[field_index] for experience in batch])
for field_index in range(5)]
return states, actions, rewards, next_states, dones
def play_one_step(env, state, epsilon):
action = epsilon_greedy_policy(state, epsilon)
next_state, reward, done, info = env.step(action)
replay_memory.append((state, action, reward, next_state, done))
return next_state, reward, done, info
batch_size = 32
discount_rate = 0.95
optimizer = keras.optimizers.Adam(lr=1e-3)
loss_fn = keras.losses.mean_squared_error
def training_step(batch_size):
experiences = sample_experiences(batch_size)
states, actions, rewards, next_states, dones = experiences
next_Q_values = model.predict(next_states)
max_next_Q_values = np.max(next_Q_values, axis=1)
target_Q_values = (rewards +
(1 - dones) * discount_rate * max_next_Q_values)
target_Q_values = target_Q_values.reshape(-1, 1)
mask = tf.one_hot(actions, n_outputs)
with tf.GradientTape() as tape:
all_Q_values = model(states)
Q_values = tf.reduce_sum(all_Q_values * mask, axis=1, keepdims=True)
loss = tf.reduce_mean(loss_fn(target_Q_values, Q_values))
grads = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
for episode in range(600):
obs = env.reset()
for step in range(200):
epsilon = max(1 - episode / 500, 0.01)
obs, reward, done, info = play_one_step(env, obs, epsilon)
if done:
break
if episode > 50:
training_step(batch_size)
타깃 Q-가치를 정하는 모델을 별도로 사용
타깃 모델: 타깃을 정의하기 위해서만 사용되는 모델. 온라인 모델의 복사본 사용.
next_Q_values = target.predict(next_states)
def training_step(batch_size):
experiences = sample_experiences(batch_size)
states, actions, rewards, next_states, dones = experiences
next_Q_values = model.predict(next_states)
best_next_actions = np.argmax(next_Q_values, axis=1)
next_mask = tf.one_hot(best_next_actions, n_outputs).numpy()
next_best_Q_values = (target.predict(next_states) * next_mask).sum(axis=1)
target_Q_values = (rewards +
(1 - dones) * discount_rate * next_best_Q_values)
target_Q_values = target_Q_values.reshape(-1, 1)
mask = tf.one_hot(actions, n_outputs)
[...] # 이전과 동일
Q-가치가 아래처럼 계산될 수 있음에 주목함.
$$ Q(s,a) = V(s) + A(s, a) $$
아래 식을 만족시키는 행동 $a^*$ 존재
$$V(s) = Q(s,a^*) \quad\text{이고}\quad A(s, a^*) = 0$$
K = keras.backend
input_states = keras.layers.Input(shape=[4])
hidden1 = keras.layers.Dense(32, activation="elu")(input_states)
hidden2 = keras.layers.Dense(32, activation="elu")(hidden1)
state_values = keras.layers.Dense(1)(hidden2)
raw_advantages = keras.layers.Dense(n_outputs)(hidden2)
advantages = raw_advantages - K.max(raw_advantages, axis=1, keepdims=True)
Q_values = state_values + advantages
model = keras.models.Model(inputs=[input_states], outputs=[Q_values])
target = keras.models.clone_model(model)
target.set_weights(model.get_weights())