이제 예제 코드를 통해 한 번 더 살펴보도록 하자.
import numpy as np
# (1) 그리드월드 환경 설정 (4x4)
grid_size = 4
gamma = 1.0 # 감가율
# (2) 상태 가치 초기화
value_table = np.zeros((grid_size, grid_size))
# (3) 종료 상태(목적지)
terminal_states = [(0, 0), (grid_size - 1, grid_size - 1)]
# (4) 가능한 행동: 상, 하, 좌, 우
actions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
# (5) 상태 전이 확률(각 방향 25%)
action_prob = 0.25
# (6) 정책 평가 함수 정의
def policy_evaluation(value_table, iterations=100):
for _ in range(iterations):
new_value_table = np.copy(value_table)
for i in range(grid_size):
for j in range(grid_size):
if (i, j) in terminal_states:
continue
value = 0
for action in actions:
next_i, next_j = i + action[0], j + action[1]
# 경계를 벗어나면 현재 위치 유지
if next_i < 0 or next_i >= grid_size or next_j < 0 or next_j >= grid_size:
next_i, next_j = i, j
reward = -1
value += action_prob * (reward + gamma * value_table[next_i, next_j])
new_value_table[i, j] = value
value_table = new_value_table
return value_table
# (7) 정책 개선 함수 정의
def policy_improvement(value_table):
policy = np.zeros((grid_size, grid_size, len(actions)))
for i in range(grid_size):
for j in range(grid_size):
if (i, j) in terminal_states:
continue
values = []
for action in actions:
next_i, next_j = i + action[0], j + action[1]
if next_i < 0 or next_i >= grid_size or next_j < 0 or next_j >= grid_size:
next_i, next_j = i, j
values.append(value_table[next_i, next_j])
best_action_value = np.max(values)
# 가장 높은 가치로 이동할 수 있는 행동 찾기
best_actions = [idx for idx, v in enumerate(values) if v == best_action_value]
# 탐욕적(Greedy) 행동 선택
for action_idx in best_actions:
policy[i, j, action_idx] = 1 / len(best_actions)
return policy
# (8) 정책 반복(평가 및 개선 반복 수행)
def policy_iteration(iterations=10):
global value_table
for _ in range(iterations):
value_table = policy_evaluation(value_table)
policy = policy_improvement(value_table)
return value_table, policy
# 실행 및 결과 출력
final_values, final_policy = policy_iteration()
# (9) 최종 상태 가치 테이블
print("최종 상태 가치 테이블:")
print(np.round(final_values, 2))
# (10) 최정 정책
print("\n최종 정책(각 상태에서의 행동 확률):")
for i in range(grid_size):
for j in range(grid_size):
print(f"상태 ({i},{j}): {final_policy[i,j]}")
최종 상태 가치 테이블:
[[ 0. -14. -20. -22.]
[-14. -18. -20. -20.]
[-20. -20. -18. -14.]
[-22. -20. -14. 0.]]
최종 정책(각 상태에서의 행동 확률):
상태 (0,0): [0. 0. 0. 0.]
상태 (0,1): [0. 0. 1. 0.]
상태 (0,2): [0. 0. 1. 0.]
상태 (0,3): [0. 0.5 0.5 0. ]
상태 (1,0): [1. 0. 0. 0.]
상태 (1,1): [0. 0. 1. 0.]
상태 (1,2): [0. 0.5 0.5 0. ]
상태 (1,3): [0. 1. 0. 0.]
상태 (2,0): [1. 0. 0. 0.]
상태 (2,1): [0.5 0. 0. 0.5]
상태 (2,2): [0. 0. 0. 1.]
상태 (2,3): [0. 1. 0. 0.]
상태 (3,0): [0.5 0. 0. 0.5]
상태 (3,1): [0. 0. 0. 1.]
상태 (3,2): [0. 0. 0. 1.]
상태 (3,3): [0. 0. 0. 0.]
다이나믹 프로그래밍
(1) 그리드월드 환경 설정
4x4 크기의 격자 형태로 구성된 환경을 만든다. 총 16개의 상태가 존재하며, 에이전트는 이 상태들 사이를 이동한다. 감가율은 1.0으로 설정되는데, 이는 미래에 받는 보상의 가치를 현재와 동일하게 평가한다는 의미다.
(2) 상태 가치 초기화
모든 상태의 가치를 0으로 시작한다. 이 가치는 앞으로의 학습을 통해 업데이트되며, 각 상태에서 출발했을 때 받을 수 있는 총 보상의 기대값을 나타낸다.
(3) 종료 상태 설정
좌상단 모서리(0,0)와 우하단 모서리(3,3)를 종료 상태로 정의한다. 이 상태에 도달하면 더 이상 움직이지 않으며, 학습 시에도 해당 상태는 건너뛴다.
(4) 가능한 행동 정의
에이전트는 상, 하, 좌, 우 네 방향으로 이동할 수 있다. 이 네 가지 행동은 각각 격자 안에서의 이동을 뜻하며, 이동한 위치가 그리드의 경계를 벗어나면 원래 위치에 머무른다.
(5) 상태 전이 확률 설정
초기 정책은 무작위 정책으로, 네 방향 중 어느 쪽으로든 25%의 확률로 움직인다. 이는 균등한 확률로 아무 방향이나 선택하는 정책을 의미하며, 이후 반복 과정을 통해 더 나은 정책으로 개선된다.
(6) 정책 평가
현재 정책에 따라 상태 가치를 계산하는 단계다. 각 상태에 대해 가능한 네 가지 행동을 모두 고려하고, 그 행동을 했을 때 도달하는 다음 상태의 가치와 보상을 기반으로 새로운 상태 가치를 계산한다. 보상은 항상 -1이며, 감가율이 1이므로 다음 상태의 가치가 그대로 반영된다. 모든 행동에 대한 기대값을 평균내어 현재 상태의 새로운 가치로 갱신한다. 이 작업을 여러 번 반복하면서 가치 테이블이 점점 더 정확해진다.
(7) 정책 개선
정책 평가 결과로 얻은 상태 가치 함수를 바탕으로 새로운 정책을 만드는 단계다. 각 상태에서 네 가지 가능한 행동 중 어떤 행동이 다음 상태의 가치가 가장 높은지를 확인하고, 그 방향을 선택한다. 만약 여러 방향이 같은 최대 가치를 준다면, 해당 방향들에 대해 확률을 균등하게 나누어 설정한다. 이렇게 만들어진 정책은 기존의 무작위 정책보다 더 좋은 방향성을 가진 탐욕적(greedy) 정책이다.
(8) 정책 반복
정책 평가와 정책 개선을 번갈아 반복하면서 점점 더 좋은 정책을 만들어가는 과정이다. 처음에는 무작위 정책으로 시작하고, 평가와 개선을 반복 수행하여 정책이 더 이상 바뀌지 않을 때까지 계속한다. 이때 도달한 정책은 최적 정책이며, 이 정책을 따르면 가능한 한 빨리 종료 상태에 도달할 수 있다. 이때의 가치 테이블은 그 최적 정책을 따를 때 각 상태에서 받을 수 있는 기대 보상의 총합을 나타낸다.
(9) 최종 상태 가치 테이블 출력
정책 반복이 완료되면 각 상태의 최종 가치가 계산된다. 이 값은 각 상태에서 출발했을 때, 최적 정책을 따를 경우 받을 수 있는 총 보상의 기대값이다. 값이 높을수록 해당 상태가 유리한 위치라는 것을 의미한다.
(10) 최종 정책 출력
최종적으로 각 상태에서 어떤 방향으로 움직이는 것이 가장 좋은지를 나타내는 정책을 출력한다. 이 정책은 네 방향에 대한 확률로 표현되며, 어떤 방향으로 움직여야 종료 상태에 더 빨리 도달할 수 있는지를 알려준다. 종료 상태의 경우 더 이상 움직임이 없으므로, 모든 방향의 확률이 0이 된다.
지금까지 설명한 내용만으로 다이나믹 프로그래밍을 완전히 이해하긴 어렵겠지만, 강화학습을 공부하는 데 있어서 이를 깊이 있게 파고들 필요는 없다. 따라서 이 정도로 개념을 짚고 넘어가는 것으로 충분하며, 앞으로 실제 알고리즘을 다루는 과정에서 자연스럽게 익히게 될 것이다.