*본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
|내용 정리|
<Q-learning 알고리즘 코드 구현 연습>
1. 기본 개념 설명(복습)
- 강화학습 (Reinforcement Learning): 에이전트(Agent)가 환경(Environment)과 상호작용하며 보상(Reward)을 최대화하는 방향으로 행동(Action)을 학습하는 머신러닝의 한 분야이다. 시행착오(Trial-and-error)를 통해 학습한다.
- 에이전트 (Agent): 학습하고 행동하는 주체 (이 코드에서는 격자 세계를 돌아다니는 개체).
- 환경 (Environment): 에이전트가 상호작용하는 외부 세계 (이 코드에서는 4x4 격자).
- 상태 (State, s): 환경의 현재 상황을 나타내는 정보 (에이전트의 위치).
- 행동 (Action, a): 에이전트가 특정 상태에서 취할 수 있는 선택지 (상, 하, 좌, 우 이동).
- 보상 (Reward, r): 에이전트가 특정 행동을 취한 결과로 환경으로부터 받는 피드백 신호 (목표 도달 시 +10, 그 외 0).
- 정책 (Policy, π): 특정 상태에서 어떤 행동을 할지 결정하는 에이전트의 전략 또는 규칙.
- 가치 함수 (Value Function): 특정 상태 또는 상태-행동 쌍이 장기적으로 얼마나 좋은지를 나타내는 함수.
- 상태-가치 함수 (State-Value Function, V(s)): 상태 s에서 시작했을 때 앞으로 받을 총 보상의 기댓값.
- 행동-가치 함수 (Action-Value Function, Q(s, a)): 상태 s에서 행동 a를 취했을 때 앞으로 받을 총 보상의 기댓값. Q-러닝은 이 Q함수를 학습한다.
- Q-러닝 (Q-Learning): 대표적인 모델-프리(Model-Free), 오프-폴리시(Off-Policy) 강화학습 알고리즘이다.
- 모델-프리: 환경의 작동 방식(상태 전이 확률, 보상 함수)을 미리 알 필요 없이 학습한다.
- 오프-폴리시: 에이전트가 실제로 따르는 행동 정책(탐험 포함)과 학습하려는 목표 정책(최적 정책)이 달라도 학습이 가능하다. Q-러닝은 항상 다음 상태에서 가능한 Q-값 중 최댓값을 사용하여 업데이트하기 때문에 오프-폴리시이다.
- Q-테이블: 상태와 행동 쌍에 대한 Q-값을 저장하는 테이블 (action_value_matrix).
- 시간차 학습 (Temporal Difference, TD): 현재 추정치를 사용하여 다음 추정치를 업데이트하는 방식. Q-러닝은 TD 학습의 일종이다. r + gamma * max_a'(Q(s', a')) 부분이 TD 타겟이다.
- Epsilon-Greedy 탐험 (Epsilon-Greedy Exploration): 학습 초반에는 무작위 행동(탐험)을 통해 다양한 경험을 쌓고, 학습이 진행됨에 따라 점차 최적이라고 생각되는 행동(활용)의 비율을 높이는 전략이다. epsilon 값이 이 비율을 조절한다.
- 학습률 (Learning Rate, α): TD 에러를 얼마나 반영하여 Q-값을 업데이트할지 결정하는 비율. 너무 크면 학습이 불안정하고, 너무 작으면 학습이 느려진다. 이 코드에서는 점차 감소시킨다.
- 할인율 (Discount Factor, γ): 미래 보상을 현재 가치로 환산할 때 얼마나 할인할지를 결정하는 값 (0~1). 1에 가까울수록 미래 보상을 중요하게 생각한다.
2. 환경 설정 (environment.py)
환경 코드는 4x4 격자 세계(Grid World)를 구현한다.
초기화 함수 (__init__)
이 코드는 강화학습 에이전트가 상호작용할 환경을 정의한다. 4x4 크기의 격자 세계(Grid World) 환경이다.
def __init__(self):
'''
state_space: 4x4 grid info using numpy
value of the agent location: 1
value of the goal location: -1
action_space: {0, 1, 2, 3}
0: up
1: right
2: down
3: left
'''
self.agent_pos = {'y': 0, 'x': 0}
self.goal_pos = {'y': 3, 'x': 3}
self.y_min, self.x_min, self.y_max, self.x_max = 0, 0, 3, 3
# set up state
self.state = np.zeros([4, 4])
self.state[self.goal_pos['y'], self.goal_pos['x']] = -1
self.state[self.agent_pos['y'], self.agent_pos['x']] = 1
self.state_space = list()
for y in range(4):
for x in range(4):
state = np.zeros([4,4])
state[self.goal_pos['y'], self.goal_pos['x']] = -1
state[y, x] = 1
self.state_space.append(state)
self.action_space = [0, 1, 2, 3]
- 에이전트 위치: (0,0)에서 시작
- 목표 위치: (3,3)에 고정
- 상태 표현: 4x4 NumPy 배열로, 에이전트 위치는 1, 목표는 -1, 나머지는 0
- 상태 공간: 에이전트가 있을 수 있는 모든 위치를 미리 계산 (총 16개 상태)
- 행동 공간: [0(상), 1(우), 2(하), 3(좌)]
환경 리셋 (reset)
def reset(self):
self.agent_pos = {'y': 0, 'x': 0}
self.state = np.zeros([4,4])
self.state[self.goal_pos['y'], self.goal_pos['x']] = -1
self.state[self.agent_pos['y'], self.agent_pos['x']] = 1
return self.state
- 에이전트 위치를 (0,0)으로 초기화
- 상태 배열 재설정 후 반환
환경 진행 (step)
def step(self, action):
# Update environmental variables
if action == 0:
# 'y' should be decreased by 1 or stay the same when it is at the top row
self.agent_pos['y'] = max(
self.agent_pos['y'] - 1,
self.y_min
)
elif action == 1:
# 'x' should be increased by 1 or stay the same when it is at the most right column
self.agent_pos['x'] = min(
self.agent_pos['x'] + 1,
self.x_max
)
elif action == 2:
# 'y' should be increased by 1 or stay the same when it is at the bottom row
self.agent_pos['y'] = min(
self.agent_pos['y'] + 1,
self.y_max
)
elif action == 3:
# 'x' should be decreased by 1 or stay the same when it is at the most left column
self.agent_pos['x'] = max(
self.agent_pos['x'] - 1,
self.x_min
)
else:
assert False, "Invalid action value was fed to step."
# Make a next state after transition
prev_state = self.state
self.state = np.zeros([4,4])
self.state[self.goal_pos['y'], self.goal_pos['x']] = -1
self.state[self.agent_pos['y'], self.agent_pos['x']] = 1
done = False
if self.agent_pos == self.goal_pos:
done = True
reward = self.reward(prev_state, action, self.state)
return reward, self.state, done
- 주어진 행동(action)에 따라 에이전트 위치 업데이트
- 격자 경계를 벗어나지 않도록 처리 (min, max 함수 사용)
- 상태 업데이트 후 이전 상태 저장
- 목표 도달 여부 확인
- 보상 계산 및 결과 반환 (보상, 다음 상태, 종료 여부)
보상 함수 (reward)
def reward(self, s, a, s_next):
reward = 0
y, x = np.where(s == 1)
y_next, x_next = np.where(s_next == 1)
if (
(y_next == self.goal_pos['y'] and x_next == self.goal_pos['x']) and
(y != self.goal_pos['y'] or x != self.goal_pos['x'])
): # Reached the goal
reward = 10
return reward
- 현재 상태와 다음 상태에서 에이전트 위치 추출
- 목표에 도달했을 때만 보상 10 지급
- 그 외에는 보상 0 (Sparse reward)
2. Q-learning 구현 (q_learning.py)
하이퍼파라미터 및 유틸리티 함수
gamma = 0.9 # 할인율
k_alpha = 2e-2 # 학습률 감쇠 계수
k_eps = 2e-4 # 탐험률 감쇠 계수
def get_state_index(state_space, state):
for i_s, s in enumerate(state_space):
if (s == state).all():
return i_s
assert False, "Couldn't find the state from the state space"
- gamma: 미래 보상의 가치를 현재 기준으로 할인하는 비율
- k_alpha: 학습률을 에피소드가 진행됨에 따라 감소시키는 계수
- k_eps: 탐험률을 스텝이 진행됨에 따라 감소시키는 계수
- get_state_index: 주어진 상태 배열이 상태 공간 내에서 어떤 인덱스에 해당하는지 찾음
Q-learning 함수
def q_learning(env):
action_value_matrix = np.zeros([len(env.state_space), len(env.action_space)])
def sample_action(eps, action_value):
a_max = action_value.argmax()
pi = np.zeros([len(env.action_space)])
pi[:] = eps / len(env.action_space)
pi[a_max] = pi[a_max] + 1 - eps
a = np.random.choice(env.action_space, p=pi)
return a
def get_eps(total_step_count):
return 1 / (1 + k_eps * total_step_count)
- action_value_matrix: Q-테이블 초기화 (16x4 크기)
- sample_action: 입실론-그리디 방식으로 행동 선택
- 확률 eps로 무작위 행동 선택
- 확률 1-eps로 최대 Q-값을 가진 행동 선택
- get_eps: 전체 스텝 수에 따라 탐험률 감소
학습 과정 구현
total_step_count = 0
for loop_count in range(2000):
done = False
step_count = 0
s = env.reset()
i_s = get_state_index(env.state_space, s)
# Generate an episode
while not done:
action_value = action_value_matrix[i_s]
eps = get_eps(total_step_count)
a = sample_action(eps, action_value)
r, s_next, done = env.step(a)
i_s_next = get_state_index(env.state_space, s_next)
alpha = 1 / (1 + k_alpha * loop_count)
td = r + gamma * action_value_matrix[i_s_next].max() - action_value_matrix[i_s][a]
action_value_matrix[i_s][a] = action_value_matrix[i_s][a] + alpha * td
if done:
action_value_matrix[i_s_next] = 0
step_count += 1
total_step_count += 1
s = s_next
i_s = i_s_next
- 2000번의 에피소드를 반복
- 각 에피소드 내부:
- 환경 초기화 (env.reset())
- 에피소드 완료될 때까지 반복:
- 현재 상태의 Q-값 가져오기
- 현재 탐험률 계산
- 행동 선택
- 환경과 상호작용 (행동 실행)
- 학습률 계산 (alpha)
- TD(Temporal Difference) 에러 계산 및 Q-값 업데이트
- 종료 상태 처리 (종료 상태의 Q-값은 0으로 설정)
- 상태 업데이트 및 카운터 증가
정책 추출 및 반환
# Generate optimal policy from the action value function
policy = np.zeros([len(env.state_space), len(env.action_space)])
state_indexes = np.arange(len(env.state_space))
argmax_actions = action_value_matrix.argmax(axis=-1)
policy[state_indexes, argmax_actions] = 1.0
return action_value_matrix, policy
- 학습된 Q-테이블에서 각 상태별 최적 행동 추출
- 확정적 정책 생성 (최적 행동에 확률 1.0 부여)
메인 실행 부분
if __name__ == "__main__":
np.set_printoptions(formatter={'float': '{: 0.3f}'.format})
env = Env()
action_value_matrix, policy = q_learning(env)
argmax_actions = action_value_matrix.argmax(axis=-1)
value_vector = np.sum(policy * action_value_matrix, axis=-1)
value_table = value_vector.reshape(4, 4)
argmax_actions_table = argmax_actions.reshape(4, 4)
print(
f"value_table: \n{value_table}\n"
+ f"argmax_actions: \n{argmax_actions.reshape(4, 4)}"
)
- 환경 생성
- Q-learning 실행
- 학습된 Q-테이블에서 최적 행동과 가치 계산
- 결과를 4x4 격자 형태로 출력
3. 코드 구조도

- 다이어그램은 Q-learning 알고리즘의 실행 흐름을 보여준다. 메인 함수에서 환경을 초기화하고, Q-learning 함수를 호출하며, 내부에서는 에피소드와 스텝을 반복하면서 Q-테이블을 업데이트하고 최종적으로 최적 정책을 추출한다.
실행 및 디버깅

출력으로 보이는 부분은 학습 과정 중 1999번째 에피소드에서의 Q-테이블(action_value_matrix) 값이다. 이 시점에서 epsilon(탐험률)은 0.2128, alpha(학습률)는 0.0257이다.
가치 테이블(value_table):
[[ 5.905 6.561 7.290 8.100]
[ 6.561 7.290 8.100 9.000]
[ 7.290 8.100 9.000 10.000]
[ 8.100 9.000 10.000 0.000]]
- 각 위치에서의 최적 가치를 보여준다.
- 목표 위치(3,3)의 가치는 0.000으로 설정되어 있다(종료 상태).
- 목표에 가까울수록 가치가 높아지는 패턴이 명확하다.
최적 행동(argmax_actions):
[[1 2 1 2]
[1 1 1 2]
[1 2 1 2]
[1 1 1 0]]
- 각 위치에서 에이전트가 취해야 할 최적 행동을 나타낸다.
- 0: 위쪽, 1: 오른쪽, 2: 아래쪽, 3: 왼쪽
- 대부분의 위치에서 오른쪽(1)이나 아래쪽(2)으로 이동하는 것이 최적인데, 이는 목표가 오른쪽 아래(3,3)에 있기 때문이다.
- 마지막 행의 마지막 열(목표 위치, 3,3)에서는 위쪽(0)으로 이동하는 것이 표시되어 있지만, 이는 사실상 의미가 없다(종료 상태이므로).
해석:
- 학습이 성공적으로 이루어졌다. 목표 지점(3,3)에 가까울수록 가치가 높게 나타나며, 최적 행동들은 대체로 목표를 향해 움직이도록 설정되었다.
- 격자의 오른쪽과 아래쪽 경계를 따라 목표로 향하는 경로가 형성되었다. 에이전트는 대부분의 위치에서 오른쪽(1)이나 아래쪽(2)으로 이동하는 것을 선호한다.
- 가치 테이블에서 목표 상태(3,3)의 가치가 0인 것은 종료 상태 처리 로직(if done: action_value_matrix[i_s_next] = 0) 때문이다.
- 최적 정책은 에이전트가 어떤 위치에서든 최소한의 스텝으로 목표에 도달할 수 있도록 형성되었다.
|인증|




-------
패스트캠퍼스 링크