행동의 점수(Q-value)를 배우는 가치 기반 강화학습
Q-learning 은 각 상태에서 어떤 행동이 얼마나 좋은지 를 점수(Q-value)로 학습하는 가치 기반(Value-Based) 강화학습 알고리즘이다. 에이전트가 환경과 상호작용하며 보상을 받을 때마다 그 행동의 Q값을 조금씩 수정하고, 반복하면 좋은 행동의 점수는 올라가고 나쁜 행동의 점수는 내려가서, 결국 가장 높은 Q값을 가진 행동 을 선택하게 된다. 이 글에서는 Q-learning의 핵심 수식과 간단한 1차원 예제, 그리고 Gym(Gymnasium) 의 CartPole 환경에서 DQN(Deep Q-Network) 으로 Q값을 신경망으로 근사하는 예제까지 정리한다.
(Q-learning - 류지 프로젝트를 바탕으로 재구성했다.)
1. Q값(Q-value)이란
Q-learning의 중심 개념은 Q값(Q-value) 이다.
Q(s, a)
- (s): 현재 상태(State)
- (a): 행동(Action)
의미:
현재 상태 (s)에서 행동 (a)를 했을 때, 앞으로 받을 것으로 기대되는 누적 보상
즉, “이 상태에서 이 행동을 하면 얼마나 이득인가?”를 숫자로 나타낸 것이다. 에이전트는 각 상태에서 Q값이 가장 큰 행동 을 선택하면 된다(탐험을 위해 ε-greedy 등을 섞어 쓰는 경우가 많다).
2. Q-learning이 작동하는 방식
학습은 다음 흐름을 반복한다.
- 현재 상태 확인
- 가능한 행동 중 하나 선택 (예: ε-greedy)
- 행동 수행
- 보상(reward) 수령
- 다음 상태 로 이동
- 방금 한 행동의 Q값 을 업데이트
이 과정을 계속 반복하면서 Q값이 점점 진짜 기대 보상 에 가까워진다.
3. Q-learning의 핵심 수식
Q-learning은 TD(0) 형태로, “현재 보상 + 다음 상태의 최대 미래 가치”를 이용해 Q값을 수정한다.
Q(s, a) ← Q(s, a) + α [ r + γ max_{a'} Q(s', a') − Q(s, a) ]
- (α): 학습률(learning rate)
- (γ): 할인율(discount factor)
- (r): 즉시 보상
- (s'): 다음 상태
- (\max_{a'} Q(s', a')): 다음 상태에서 가장 큰 Q값 (목표 상태이면 보통 0으로 둠)
즉,
현재 행동의 가치를 “현재 보상 + 할인된 다음 상태의 최대 Q값”으로 조금씩 보정하는 과정 이다.
4. 간단한 1차원 환경 예제
다음과 같은 1차원 환경을 생각하자.
S . . . G
0 1 2 3 4
- 시작 상태: 0
- 목표 상태: 4 (Goal)
- 목표 도착 보상: +10
- 한 칸 이동 보상: -1
- 행동: 왼쪽(0), 오른쪽(1)
초기에는 모든 Q값을 0으로 둔다.
4-1. 예제 1: 상태 3에서 오른쪽 이동
- 현재 상태: (s = 3)
- 행동: (a =) 오른쪽
- 이동 결과: 3 → 4 (Goal)
- 보상: (r = +10)
- 다음 상태: (s' = 4)
Goal은 종료 상태 이므로 더 이상 움직이지 않는다. 따라서 (\max Q(s', a') = 0) 으로 둔다.
- (α = 0.1), (γ = 0.9)
- 목표값: (r + γ \times 0 = 10 + 0 = 10)
- 업데이트: (Q(3, \text{오른쪽}) \leftarrow 0 + 0.1 \times (10 - 0) = 1)
→ 상태 3에서 오른쪽 행동의 Q값이 1로 증가 한다.
4-2. 예제 2: 상태 2에서 오른쪽 이동
- (s = 2), (a =) 오른쪽 → (s' = 3), (r = -1)
- 방금 구한 값: (Q(3, \text{오른쪽}) = 1) 이므로 (\max Q(3, a') = 1)
- 목표값: (-1 + 0.9 \times 1 = -0.1)
- (Q(2, \text{오른쪽}) \leftarrow 0 + 0.1 \times (-0.1 - 0) = -0.01)
이렇게 목표(Goal) 쪽 Q값이 먼저 올라가고, 그 값이 이전 상태로 전파 되면서, “목표로 가는 방향”의 Q값이 점점 학습된다.
5. Q-learning 계산 과정 프로그램 구현
같은 1차원 환경에서 ε-greedy 로 행동을 선택하고, Q-learning 수식으로 Q테이블을 업데이트하는 코드다. Q값이 어떻게 바뀌고 보상이 뒤쪽 상태로 전파되는지 확인할 수 있다.
- 학습률 (α = 0.5), 할인율 (γ = 0.9), 탐험 확률 (ε = 0.2)
- 확률 (ε): 랜덤 행동
- 확률 (1-ε): Q값이 가장 큰 행동 선택
import numpy as np
import random
num_states = 5
actions = [0, 1] # 0: 왼쪽, 1: 오른쪽
learning_rate = 0.5
discount_factor = 0.9
epsilon = 0.2
episodes = 100
q_table = np.zeros((num_states, len(actions)))
def get_action(state):
if random.random() < epsilon:
return random.choice(actions)
else:
return np.argmax(q_table[state])
def step(state, action):
if action == 1:
next_state = min(state + 1, num_states - 1)
else:
next_state = max(state - 1, 0)
if next_state == 4:
reward = 10
done = True
else:
reward = -1
done = False
return next_state, reward, done
for episode in range(episodes):
state = 0
done = False
while not done:
action = get_action(state)
next_state, reward, done = step(state, action)
old_value = q_table[state, action]
next_max = np.max(q_table[next_state]) if not done else 0.0
q_table[state, action] = old_value + learning_rate * (
reward + discount_factor * next_max - old_value
)
state = next_state
print("최종 Q-테이블:")
print(q_table)
에피소드를 충분히 돌리면, 목표(4) 쪽으로 갈수록 오른쪽 행동의 Q값이 더 높아지는 패턴이 나온다.
6. Gym(Gymnasium)이란
Gym(현재는 Gymnasium 이 계승)은 강화학습 알고리즘을 실험하기 위한 표준 환경(environment) 을 제공하는 파이썬 라이브러리다.
- 에이전트가 상태 를 관찰하고 행동 을 선택하면, 환경이 다음 상태 와 보상 을 반환한다.
- CartPole, MountainCar, Atari 등 다양한 환경을 제공해, 알고리즘 비교·실습에 널리 쓰인다.
설치 예시:
pip install gymnasium
7. DQN: CartPole-v1에서 Q값을 신경망으로 근사
DQN(Deep Q-Network) 은 Q값 (Q(s,a)) 를 테이블이 아니라 신경망 으로 근사하는 방법이다. DQN 논문
- CartPole-v1: 카트 위 막대가 쓰러지지 않도록 카트를 왼쪽(0)·오른쪽(1) 으로 움직이는 환경.
- 상태: 4차원 (카트 위치, 카트 속도, 막대 각도, 막대 각속도)
- 행동: 0(왼쪽), 1(오른쪽)
- 에이전트는 상태 4개 값 → 신경망 → 행동 2개에 대한 Q값 을 출력하고, 그 Q값을 Q-learning 식으로 업데이트한다.
7-1. Replay Buffer
DQN에서는 경험 (s, a, r, s', done) 을 버퍼에 쌓아 두고, 랜덤하게 mini-batch 를 뽑아 학습한다. 이렇게 하면 연속된 샘플 간 상관을 줄이고 학습이 안정된다.
import collections
import random
buffer_limit = 50000
batch_size = 32
class ReplayBuffer:
def __init__(self):
self.buffer = collections.deque(maxlen=buffer_limit)
def put(self, transition):
self.buffer.append(transition)
def sample(self, n):
mini_batch = random.sample(self.buffer, n)
s_lst, a_lst, r_lst, s_prime_lst, done_mask_lst = [], [], [], [], []
for transition in mini_batch:
s, a, r, s_prime, done_mask = transition
s_lst.append(s)
a_lst.append([a])
r_lst.append([r])
s_prime_lst.append(s_prime)
done_mask_lst.append([done_mask])
return (
torch.tensor(s_lst, dtype=torch.float),
torch.tensor(a_lst),
torch.tensor(r_lst),
torch.tensor(s_prime_lst, dtype=torch.float),
torch.tensor(done_mask_lst),
)
def size(self):
return len(self.buffer)
7-2. Q 네트워크와 ε-greedy 행동 선택
상태 4차원 → FC 128 → ReLU → FC 128 → ReLU → FC 2 (행동 2개 Q값).
import torch
import torch.nn as nn
import torch.nn.functional as F
class Qnet(nn.Module):
def __init__(self):
super(Qnet, self).__init__()
self.fc1 = nn.Linear(4, 128)
self.fc2 = nn.Linear(128, 128)
self.fc3 = nn.Linear(128, 2)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def sample_action(self, obs, epsilon):
out = self.forward(obs)
if random.random() < epsilon:
return random.randint(0, 1)
else:
return out.argmax().item()
7-3. 학습: TD 타깃과 Huber Loss
Q-learning 수식을 그대로 쓰되, 타깃 네트워크 (q_{\text{target}}) 로 다음 상태의 최대 Q값을 계산해 타깃 을 만든다. 종료 상태면 미래 가치를 0으로 두기 위해 done_mask 를 곱한다.
def train(q, q_target, memory, optimizer):
for i in range(10):
s, a, r, s_prime, done_mask = memory.sample(batch_size)
q_out = q(s)
q_a = q_out.gather(1, a)
max_q_prime = q_target(s_prime).max(1)[0].unsqueeze(1)
target = r + gamma * max_q_prime * done_mask
loss = F.smooth_l1_loss(q_a, target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
q_out.gather(1, a): 각 샘플에서 선택한 행동 (a) 에 해당하는 Q값만 뽑는다.done_mask: done이면 0, 아니면 1 → 종료 시 타깃이 (r) 만 남게 한다.
7-4. 전체 학습 루프 (Gymnasium CartPole-v1)
import gymnasium as gym
import torch.optim as optim
learning_rate = 0.0005
gamma = 0.98
print_interval = 20
def main():
env = gym.make('CartPole-v1')
q = Qnet()
q_target = Qnet()
q_target.load_state_dict(q.state_dict())
memory = ReplayBuffer()
score = 0.0
optimizer = optim.Adam(q.parameters(), lr=learning_rate)
for n_epi in range(10000):
epsilon = max(0.01, 0.08 - 0.01 * (n_epi / 200))
s, info = env.reset()
done = False
while not done:
a = q.sample_action(torch.from_numpy(s).float(), epsilon)
s_prime, r, done, truncated, info = env.step(a)
done_mask = 0.0 if (done or truncated) else 1.0
memory.put((s, a, r / 100.0, s_prime, done_mask))
s = s_prime
score += r
if done or truncated:
break
if memory.size() > 2000:
train(q, q_target, memory, optimizer)
if n_epi % print_interval == 0 and n_epi != 0:
q_target.load_state_dict(q.state_dict())
print("n_episode: {}, score: {:.1f}, n_buffer: {}, eps: {:.1f}%".format(
n_epi, score / print_interval, memory.size(), epsilon * 100
))
score = 0.0
env.close()
if __name__ == '__main__':
main()
- ε: 초반 8% 근처에서 시작해 점점 줄여 최소 1% 로 둔다 (탐험 → 활용).
- 타깃 네트워크: 주기적으로 (q) 의 가중치를 (q_{\text{target}}) 에 복사해, 타깃이 너무 흔들리지 않게 한다.
- Replay Buffer: 2000개 이상 쌓인 뒤부터 학습을 시작한다.
8. 정리
| 주제 | 핵심 포인트 |
|---|---|
| Q(s,a) | 상태 (s)에서 행동 (a)를 했을 때 기대되는 누적 보상. |
| Q-learning | (Q(s,a) \leftarrow Q(s,a) + α[r + γ \max_{a'} Q(s',a') - Q(s,a)]). TD(0) 기반, off-policy. |
| ε-greedy | 확률 (ε)로 랜덤 행동(탐험), (1-ε)로 최대 Q 행동(활용). |
| DQN | Q값을 신경망으로 근사. Replay Buffer + 타깃 네트워크로 안정화. |
| Gym/Gymnasium | 강화학습용 표준 환경. CartPole 등으로 알고리즘 실험. |
마치며
- Q-learning은 행동의 점수(Q값) 를 배우고, 그 점수가 가장 높은 행동을 선택하는 가치 기반 강화학습의 대표 알고리즘이다.
- 1차원 같은 작은 환경에서는 Q 테이블 만으로도 동작을 확인할 수 있고, CartPole 처럼 연속 상태 공간에서는 DQN 처럼 신경망으로 Q값을 근사해야 한다.
- Replay Buffer와 타깃 네트워크는 DQN 학습 안정성의 핵심 요소이므로, 코드에서 각각 “왜 쓰는지”를 이해해 두면 좋다.
'현재 > 강화학습' 카테고리의 다른 글
| A3C - Asynchronous Advantage Actor-Critic (0) | 2026.03.16 |
|---|---|
| Policy 기반 에이전트 - REINFORCE, Actor-Critic, TD Actor-Critic (0) | 2026.03.13 |
| Deep RL - 함수 근사, 신경망, 가치 기반·정책 기반 강화학습 (0) | 2026.03.11 |
| Monte Carlo와 TD Learning - GridWorld로 비교하는 모델 프리 가치 학습 (0) | 2026.03.10 |
| 벨만 기대 방정식 - 술취한 사람 예제로 이해하는 가치 함수와 값 반복 (0) | 2026.03.09 |